You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1767 lines
63 KiB
1767 lines
63 KiB
#!/usr/bin/python |
|
""" |
|
|
|
xmpp-ircd, IRC to Jabber/XMPP gateway. |
|
forked from Telepaatti |
|
|
|
Copyright (C) 2007-2009 Petteri Klemola |
|
Copyright (C) 2015 moparisthebest |
|
|
|
xmpp-ircd is free software; you can redistribute it and/or modify it |
|
under the terms of the GNU General Public License version 3 as |
|
published by the Free Software Foundation. |
|
|
|
xmpp-ircd is distributed in the hope that it will be useful, but |
|
WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program; if not, write to the Free Software |
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
02110-1301, USA. |
|
""" |
|
|
|
import socket |
|
import ssl |
|
import time, datetime |
|
import exceptions |
|
from threading import * |
|
from xmpp import * |
|
import getopt, sys |
|
import daemon |
|
import logging |
|
import logging.handlers |
|
import urllib |
|
import string |
|
import random |
|
|
|
STATUSSTATES = ['AVAILABLE','CHAT', 'AWAY', 'XA', 'DND', 'INVISIBLE'] |
|
XMPPIRCDVERSION = 3.0 |
|
|
|
class JabberThread(Thread): |
|
"""Class for Jabber connection thread""" |
|
|
|
def __init__(self, client): |
|
"""Constructor for JabberThread Class |
|
|
|
@type client: Client |
|
@param client: instace of xmpp.Client class |
|
""" |
|
Thread.__init__(self) |
|
self.client = client |
|
self.connected = True |
|
|
|
def run(self): |
|
"""When xmpp client is connected runs the client process """ |
|
#return |
|
time.sleep(5) |
|
while self.client.Process(1) and self.connected: |
|
pass |
|
self.client.disconnect() |
|
self.connected = False |
|
|
|
class ClientThread(Thread): |
|
""" ClientThread class for handling IRC and Jabber connections.""" |
|
def __init__(self,socket, port, server, muc_server, component): |
|
"""Constructor for ClientThread class |
|
|
|
@type socket: socket |
|
@type port: integer |
|
@type server: server part of JID |
|
@param socket: socket on which the connection is made |
|
@param port: port of the connection |
|
""" |
|
Thread.__init__(self) |
|
|
|
self.fullRoomJid = False |
|
|
|
self.component = component |
|
|
|
self.socket = socket |
|
self.port = port |
|
self.server = server |
|
self.muc_server = muc_server |
|
|
|
self.passwd= None |
|
|
|
self.nickname = None |
|
|
|
self.newnick = '' |
|
|
|
self.mucs = {} |
|
|
|
self.connected = True |
|
|
|
self.UIDtoJID = {} |
|
|
|
self.nickChangeInMucs = {} |
|
|
|
self.joinQueue = {} |
|
self.roomPingQueue = {} |
|
self.disconnectedMucs = {} |
|
self.changingNick = {} |
|
self.pingCounter = 0 |
|
|
|
def printError(self, msg): |
|
"""Error message printing for std out |
|
|
|
@type msg: string |
|
@param msg: error message |
|
""" |
|
self.component.logger.error(msg) |
|
|
|
def printDebug(self, msg): |
|
"""print Debug message to std out |
|
|
|
@type msg: string |
|
@param msg: debug message |
|
""" |
|
self.component.logger.debug(msg) |
|
|
|
def getMucs(self): |
|
"""Return joined MUC without roster MUC |
|
|
|
@rtype: list |
|
@return: list of mucs joined (without roster) |
|
""" |
|
return self.mucs |
|
|
|
def fixNick(self, nick): |
|
"""Fixes strange character nicknames that don't work nicely with |
|
IRC. This function may cause conflicts and thus unfinished. |
|
|
|
@type nick: string |
|
@param nick: nickname to fix |
|
@rtype: string |
|
@return: fixed nick |
|
""" |
|
|
|
fixednick = unicode(nick) |
|
fixednick = fixednick.replace(' ', '_') |
|
fixednick = fixednick.replace('!', '_') |
|
fixednick = fixednick.replace(':', '_') |
|
fixednick = fixednick.replace('@', '_') |
|
return fixednick |
|
|
|
def fixChannel(self, channel): |
|
# fix roomname |
|
if self.fullRoomJid: |
|
return channel |
|
channel = unicode(channel) |
|
return channel[0:channel.find('@')] |
|
|
|
def fixChannelCommand(self, arguments): |
|
# do the opposite of fixChannel() above, and strip # |
|
if self.fullRoomJid: |
|
return arguments[1:] |
|
if ' ' in arguments: |
|
return arguments[1:].replace(' ', "@%s " % (self.muc_server), 1) |
|
else: |
|
return "%s@%s" % (arguments[1:], self.muc_server) |
|
|
|
def makeHostFromJID(self, jid): |
|
""" builds the host part from a given jid |
|
|
|
@type jid: JID |
|
@param jid: The JID from which to make the host part |
|
@rtype string |
|
@return valid Host part |
|
""" |
|
|
|
return "%s@%s/%s" % (urllib.quote(jid.getNode()), urllib.quote(jid.getDomain()), urllib.quote(jid.getResource())) |
|
|
|
|
|
def makeNickFromJID(self, jid, is_muc_jid): |
|
""" builds a nick from a given jid |
|
|
|
@type jid: JID |
|
@type is_muc_jid: boolean |
|
@param jid: The JID from which to make a nick |
|
@param is_muc_jid: Whethr this JID belongs to a MUC |
|
@rtype:string |
|
@return: valid IRC nick |
|
""" |
|
|
|
if is_muc_jid and not jid.getResource(): |
|
return self.fixNick(jid.getNode()) |
|
|
|
jid_string = u'' |
|
if not is_muc_jid: |
|
jid_string = jid.getStripped() |
|
else: |
|
jid_string = unicode(jid) |
|
|
|
if not is_muc_jid: |
|
nick = self.fixNick(jid.getNode()) |
|
else: |
|
nick = self.fixNick(jid.getResource()) |
|
|
|
self.UIDtoJID[nick] = JID(jid_string) |
|
|
|
return nick |
|
|
|
def getJIDFromNick(self, nick): |
|
"""Reverses obtains a JID corresponding to a nick generated by makeNickFromJID |
|
|
|
@type nick: string |
|
@param nick: nickname from which to recover a JID |
|
@rtype: JID |
|
@return: JID corresponding to nick |
|
""" |
|
|
|
if self.UIDtoJID.has_key(nick): |
|
return self.UIDtoJID[nick] |
|
if nick.find('@') != -1: |
|
return JID(nick) |
|
|
|
return None |
|
|
|
def makeIRCACTION(self, msg): |
|
"""Makes IRC action message |
|
|
|
@type msg: string |
|
@param msg: message from which to make IRC action |
|
@rtype: string |
|
@return: IRC action message |
|
""" |
|
msg = '\001ACTION %s\001' % msg |
|
return msg |
|
|
|
def sendToIRC(self, msg): |
|
"""Sends message IRC client |
|
|
|
@type msg: string |
|
@param msg: message to send |
|
""" |
|
msg = msg.encode('utf-8') |
|
msg = "%s\r\n" % msg |
|
self.printDebug(msg) |
|
try: |
|
self.socket.send(msg) |
|
except: |
|
self.connected = False |
|
self.printError('Fatal error while trying to write irc message to socket, disconnecting [%s - %s]' % (sys.exc_info()[0], sys.exc_info()[1])) |
|
|
|
def sendToXMPP(self, msg): |
|
"""Sends message XMPP server |
|
|
|
@type msg: string |
|
@param msg: message to send |
|
""" |
|
msg.setFrom(self.JID) |
|
self.component.send(msg) |
|
|
|
def ircGetStatus(self, jid, room_jid): |
|
"""Get IRC status |
|
|
|
@type jid: JID |
|
@type room_jid: JID |
|
@param jid: JID of the user whose status should be obtained |
|
@param room_jid: the room for which to get the status |
|
@rtype: string |
|
@return: IRC-style status string |
|
""" |
|
sta = 'H' |
|
show = '' |
|
role = '' |
|
if self.mucs.has_key(room_jid): |
|
show = self.mucs[room_jid][jid]['show'] |
|
role = self.mucs[room_jid][jid]['role'] |
|
if show in ['away','xa', 'dnd']: |
|
sta = 'G' |
|
if role == 'moderator': |
|
sta = '%s@' % sta |
|
elif role == 'participant': |
|
sta = '%s+' % sta |
|
return sta |
|
|
|
def ircCommandJOIN(self, jid): |
|
"""IRC command join channel |
|
|
|
@type jid: JID |
|
@param jid: The MUC JID for which to generate a JOIN message |
|
""" |
|
nick = self.makeNickFromJID(jid, True) |
|
channel = jid.getStripped() |
|
msg = ':%s!%s JOIN :#%s' % ( |
|
nick, |
|
self.makeHostFromJID(jid), |
|
self.fixChannel(channel)) |
|
self.sendToIRC(msg) |
|
|
|
role = self.mucs[channel][jid]['role'] |
|
args = '' |
|
if role == 'moderator': |
|
args = '+o' |
|
elif role == 'participant': |
|
args = '+v' |
|
if args: |
|
self.ircCommandMODEMUCUSER(JID(channel), jid, args) |
|
|
|
def ircCommandSELFJOIN(self, room_jid): |
|
"""IRC command join channel |
|
|
|
@type room_jid: JID |
|
@param room_jid: JID of the room we want to join |
|
""" |
|
snick = self.nickname |
|
lines = list() |
|
channel = self.fixChannel(room_jid) |
|
lines.append(':%s JOIN :#%s'% (snick, channel)) |
|
lines.append(':%s MODE #%s +n' % (self.server, channel)) |
|
|
|
for jid in self.mucs[room_jid].iterkeys(): |
|
nick = snick |
|
if (jid.getResource() != nick): |
|
nick = self.makeNickFromJID(jid, True) |
|
if self.mucs[room_jid][jid]['role'] == 'moderator': |
|
nick = "@%s" % nick |
|
elif self.mucs[room_jid][jid]['role'] == 'participant': |
|
nick = "+%s" % nick |
|
lines.append(':%s 353 %s = #%s :%s' % (self.server, snick, channel, nick)) |
|
lines.append(':%s 366 %s #%s :End of /NAMES list.'% (self.server, snick, channel)) |
|
while lines: |
|
msg = lines.pop(0) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandPART(self, jid, text): |
|
"""IRC command part channel |
|
|
|
@type jid: JID |
|
@type text: string |
|
@param jid: The MUC JID for which to generate a PART message |
|
@param text: the part message |
|
""" |
|
nick = self.makeNickFromJID(jid, True) |
|
msg = ':%s!%s PART #%s :%s' % ( |
|
nick, |
|
self.makeHostFromJID(jid), |
|
self.fixChannel(jid.getStripped()), |
|
text) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandNICK(self, old_jid, new_jid): |
|
"""Reports a nick change to the IRC client |
|
|
|
@type old_jid: JID |
|
@type new_jid: JID |
|
@param old_jid: JID before the nick change |
|
@param new_jid: JID after the nick change |
|
""" |
|
|
|
msg = ':%s!%s NICK :%s' % ( |
|
self.makeNickFromJID(old_jid, True), |
|
self.makeHostFromJID(old_jid), |
|
self.makeNickFromJID(new_jid, True)) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandPRIVMSG(self, jid, is_muc, is_private, text, timestamp=''): |
|
"""Converts private messages to IRC client |
|
|
|
@type jid: JID |
|
@type is_muc: boolean |
|
@type text: string |
|
@type timestamp: string |
|
@param jid: the JID from which the mesage was sent |
|
@param is_muc: whether the message was sent in a muc |
|
@param text: the message |
|
@param timestamp: timestamp of the message |
|
""" |
|
nick = self.makeNickFromJID(jid, is_muc) |
|
lines = text.splitlines() |
|
messages = list() |
|
for line in lines: |
|
action = False |
|
if line.upper().startswith('/ME '): |
|
line = line[4:] |
|
action = True |
|
if timestamp: |
|
line = "[%s] %s " % (timestamp, line) |
|
if action: |
|
line = self.makeIRCACTION(line) |
|
|
|
if is_muc and not is_private: |
|
msg = ':%s!%s PRIVMSG #%s :%s' % (nick, self.makeHostFromJID(jid), self.fixChannel(jid.getStripped()), line) |
|
else: |
|
msg = ':%s!%s PRIVMSG %s :%s' % (nick, self.makeHostFromJID(jid), self.nickname,line) |
|
messages.append(msg) |
|
for msg in messages: |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandTOPIC(self, jid, topic): |
|
"""Converts MUC topic to IRC channel topic |
|
|
|
@type jid: JID |
|
@type topic: string |
|
@param jid: The jid who initiated the topic change |
|
@param topic: the topic |
|
""" |
|
nick = self.makeNickFromJID(jid, True) |
|
msg =':%s!%s TOPIC #%s :%s' % (nick, self.makeHostFromJID(jid), self.fixChannel(jid.getStripped()), topic) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandMODEMUC(self, room_jid, args): |
|
"""Converts MUC mode to IRC channel mode |
|
|
|
@type room_jid: JID |
|
@type args: string |
|
@param room_jid: JID of the room |
|
@param args: arguments of the mode |
|
""" |
|
nick = self.nickname |
|
channel = self.fixChannel(room_jid) |
|
msg = ':%s 324 %s #%s %s' % (self.server, nick, channel, args) |
|
self.sendToIRC(msg) |
|
msg = ':%s 329 %s #%s %s' % (self.server, nick, channel, '1031538353') |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandMODEMUCBANLIST(self, room_jid): |
|
"""Converts MUC ban list to IRC channel banlist, is unfinished |
|
|
|
@type room_jid: JID |
|
@param room_jid: JID of the room |
|
""" |
|
nick = self.nickname |
|
msg = ':%s 368 %s #%s :End of Channel Ban List' % (self.server, nick, self.fixChannel(room_jid)) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandMODEMUCUSER(self, giver, taker, args): |
|
"""Convers MUC user mode to IRC channel user mode. Example use cases |
|
are when someone is granted a voice or admin rights on a MUC. |
|
|
|
@type giver: JID |
|
@type taker: JID |
|
@type args: string |
|
@param giver: The user initiating the mode change |
|
@param taker: The user affected by the mode change |
|
@param args: arguments of the mode |
|
""" |
|
msg = ':%s!%s MODE #%s %s %s' % (self.makeNickFromJID(giver, True), |
|
self.makeHostFromJID(giver), |
|
self.fixChannel(taker.getStripped()), |
|
args, |
|
self.makeNickFromJID(taker, True)) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandMODE(self, args): |
|
"""Converts XMPP mode to IRC mode. Unfinished |
|
|
|
@type args: string |
|
@param args: arguments of the mode |
|
""" |
|
# just to keep irssi happy, fix later to more meaningfull |
|
msg = ':%s MODE %s :%s' % (self.nickname, self.nickname, args) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandERROR(self, message='', ircerror=0): |
|
"""Convert XMPP errors to IRC errors |
|
|
|
@type message: string |
|
@type ircerror: integer |
|
@param message: the error message |
|
@param ircerror: number of the error |
|
""" |
|
msg = '' |
|
if ircerror == 0: |
|
msg = 'ERROR :XMPP ERROR %s' % message |
|
elif ircerror == -1: |
|
msg = 'ERROR :xmpp-ircd error %s' % message |
|
elif ircerror == 403: |
|
msg = ':%s 403 %s %s :That channel doesn\'t exist' % (self.server, self.nickname, self.server) |
|
elif ircerror == 464: |
|
msg = ':%s 464 :Password incorrect' % (self.server) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandERRORMUC(self, number, errormess, room): |
|
"""Convert XMPP MUC errors to IRC channel errors |
|
|
|
@type number: integer |
|
@type errormess: string |
|
@type room: string |
|
@param number: number of the error |
|
@param errormess: the error message |
|
@param room: the MUC |
|
""" |
|
text = '' |
|
if number == 403: |
|
text = 'No such channel' |
|
elif number == 404: |
|
text = 'Cannot send to channel' |
|
elif number == 467: |
|
text = 'Channel key already set' |
|
elif number == 471: |
|
text = 'Cannot join channel (+l)' |
|
elif number == 473: |
|
text = 'Cannot join channel (+i)' |
|
elif number == 474: |
|
text = 'Cannot join channel (+b)' |
|
elif number == 475: |
|
text = 'Cannot join channel (+k)' |
|
elif number == 476: |
|
text = 'Bad Channel Mask' |
|
elif number == 477: |
|
text = 'Channel doesn\'t support modes' |
|
elif number == 478: |
|
text = 'Channel list is full' |
|
elif number == 481: |
|
text = 'Permission Denied- You\'re not an IRC operator' |
|
elif number == 482: |
|
text = 'You\'re not channel operator' |
|
else: |
|
text = 'No text' |
|
|
|
msg = ':%s %s %s #%s :%s' % ( |
|
self.server, number, self.nickname, self.fixChannel(room), text) |
|
self.sendToIRC(msg) |
|
self.ircCommandERROR(errormess) |
|
|
|
def ircCommandWHO(self, users, room_jid): |
|
"""Convert XMPP vcard query to IRC who |
|
|
|
@type users: list |
|
@type room_jid: JID |
|
@param users: list of users |
|
@param room_jid: the JID of the room |
|
""" |
|
channel = self.fixChannel(room_jid) |
|
for user in users: |
|
nick = self.makeNickFromJID(user, True), |
|
msg = ':%s 352 %s #%s %s %s %s %s %s :0 %s' % ( |
|
self.server, |
|
self.nickname, |
|
channel, |
|
user.getResource(), |
|
user.getDomain(), |
|
self.server, |
|
nick, |
|
self.ircGetStatus(user, room_jid), |
|
self.fixNick(user.getResource())) |
|
self.sendToIRC(msg) |
|
|
|
msg = ':%s 315 %s #%s :End of /WHO list.' % (self.server, self.nickname, channel) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandWHOIS(self, jid): |
|
"""Convert XMPP vcard query to IRC whois |
|
|
|
@type jid: JID |
|
@param jid: Jabber id of the user whose vcard is quered |
|
""" |
|
nick = self.makeNickFromJID(jid, self.mucs.has_key(jid.getStripped())) |
|
lines = [ |
|
':%s 311 %s %s %s %s * : %s' % ( |
|
self.server, |
|
self.nickname, |
|
nick, |
|
jid.getNode(), |
|
jid.getDomain(), |
|
nick), |
|
':%s 312 %s %s %s : XMPP xmpp-ircd' % (self.server, self.nickname, nick, self.server), |
|
':%s 318 %s %s :End of /WHOIS list.' % (self.server, self.nickname, nick)] |
|
while lines: |
|
self.sendToIRC(lines.pop(0)) |
|
|
|
def ircCommandLIST(self, channels): |
|
"""Convert XMPP query to IRC channel list |
|
|
|
@type channels: list |
|
@param channels: list of channels |
|
""" |
|
msg = ':%s 321 %s Channel :Users Name' % (self.server, self.nickname) |
|
self.sendToIRC(msg) |
|
|
|
for channel in channels: |
|
# todo: implement # visible and topic |
|
msg = ':%s 322 %s #%s 5 :Unknown' % ( |
|
self.server, |
|
self.nickname, |
|
channel) |
|
self.sendToIRC(msg) |
|
|
|
msg = ':%s 323 %s :End of /LIST' % (self.server, self.nickname) |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandUNAWAY(self): |
|
"""Convert XMPP status to IRC away""" |
|
nick = self.nickname |
|
msg = ':%s 305 %s :%s' % ( |
|
self.server, |
|
nick, |
|
'You are no longer marked as being away') |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandNOWAWAY(self): |
|
"""Convert XMPP status to IRC not away""" |
|
nick = self.nickname |
|
msg = ':%s 306 %s :%s' % ( |
|
self.server, |
|
nick, |
|
'You have been marked as being away') |
|
self.sendToIRC(msg) |
|
|
|
def ircCommandNOTICE(self, text): |
|
"""Convert XMPP message to IRC notice |
|
|
|
@type text: string |
|
@param text: notice text |
|
""" |
|
nick = self.nickname |
|
msg = 'NOTICE %s :%s' % (nick, text) |
|
self.sendToIRC(msg) |
|
|
|
def xmppCommandMUCMODE(self, jid): |
|
"""Send XMPP MUC mode change |
|
|
|
@type jid: string |
|
@param jid: Jabber id of the MUC |
|
""" |
|
iq = protocol.Iq(to=jid, |
|
queryNS=NS_DISCO_INFO, |
|
typ = 'get') |
|
iq.setID('disco3') |
|
self.sendToXMPP(iq) |
|
|
|
def xmppCommandMUCUSERS(self, jid): |
|
"""Send XMPP MUC users query |
|
|
|
@type jid: string |
|
@param jid: Jabber id of the MUC |
|
""" |
|
iq = protocol.Iq(to=jid, |
|
queryNS=NS_DISCO_ITEMS, |
|
typ = 'get') |
|
iq.setID('disco_muc_users') |
|
self.sendToXMPP(iq) |
|
|
|
def xmppCommandMUCROOMS(self): |
|
"""Send XMPP MUC rooms query |
|
""" |
|
iq = protocol.Iq(to=self.muc_server, |
|
queryNS=NS_DISCO_ITEMS, |
|
typ = 'get') |
|
iq.setID('disco_muc_rooms') |
|
self.sendToXMPP(iq) |
|
|
|
def xmppCommandSTATUS(self, show, status): |
|
"""Send XMPP status change |
|
|
|
@type show: string |
|
@type status: string |
|
@param show: status |
|
@param status: status |
|
""" |
|
for muc in self.mucs.keys(): |
|
p=Presence(to='%s/%s' % ( |
|
muc, |
|
self.nickname)) |
|
if not show == '': |
|
p.setShow(show) |
|
p.setStatus(status) |
|
self.sendToXMPP(p) |
|
p=Presence() |
|
p.setShow(show) |
|
p.setStatus(status) |
|
self.sendToXMPP(p) |
|
if show.upper() in STATUSSTATES[2:]: # available, chat |
|
self.ircCommandNOWAWAY() |
|
else: |
|
self.ircCommandUNAWAY() |
|
|
|
def xmppCommandMUCPRESENCE(self, muc, nick): |
|
"""Send XMPP presence to MUC room |
|
|
|
@type muc: string |
|
@type nick: string |
|
@param muc: Jabber ID of the MUC |
|
@param nick: users nickname for the MUC |
|
""" |
|
self.sendToXMPP(Presence(to='%s/%s' % |
|
(muc, |
|
nick))) |
|
|
|
def xmppCommandMUCROLE(self, muc, nick, role): |
|
"""Send XMPP MUC role to MUC room |
|
|
|
@type muc: string |
|
@type nick: string |
|
@type role: role |
|
@param muc: Jabber ID of the MUC |
|
@param nick: users nickname in the MUC |
|
@param role: role of the user |
|
""" |
|
iq = protocol.Iq(to=muc, |
|
queryNS=NS_MUC_ADMIN, |
|
typ = 'set') |
|
item = iq.getTag('query').setTag('item') |
|
item.setAttr('nick', nick) |
|
item.setAttr('role', role) |
|
self.sendToXMPP(iq) |
|
|
|
def xmppCommandGETWHOIS(self, jid): |
|
"""Send XMPP vcard, last activity and sofware version request for some |
|
Jabber ID. |
|
|
|
@type jid: string |
|
@param jid: Jabber ID |
|
""" |
|
# vcard |
|
iq = protocol.Iq(to=jid, |
|
typ = 'get') |
|
iq.setTag(NS_VCARD + ' vCard') |
|
iq.setID('v3') |
|
self.sendToXMPP(iq) |
|
|
|
# last activity |
|
iq = protocol.Iq(to=jid, |
|
typ = 'get', |
|
queryNS=NS_LAST) |
|
self.sendToXMPP(iq) |
|
|
|
# software version |
|
# todo: looks at roster which doesn't exist, fix this |
|
if not jid.getResource(): |
|
# try to find match in roster |
|
rosternicks = self.mucs['roster'].keys() |
|
for x in rosternicks: |
|
if x.getStripped() == jid.getStripped(): |
|
jid = x |
|
if not jid.getResource(): |
|
return |
|
iq = protocol.Iq(to=jid, |
|
typ = 'get', |
|
queryNS=NS_VERSION) |
|
self.sendToXMPP(iq) |
|
|
|
def xmppCommandINFOGET(self, jid): |
|
"""Not finished """ |
|
pass |
|
|
|
def xmppCommandSOFTWAREVERSION(self, jid): |
|
"""Send set software version XMPP. Not finished """ |
|
pass |
|
|
|
def xmppCommandLASTACTIVITY(self, jid): |
|
"""Send last activity XMPP. Not finished """ |
|
pass |
|
|
|
def run(self): |
|
self.component.registerJid(self) |
|
|
|
while self.connected and self.nickname is None: |
|
try: |
|
data = self.socket.recv(4096) |
|
except: |
|
self.printError('Not receiving enough data from socket') |
|
self.connected = False |
|
if data: |
|
for line in data.splitlines(): |
|
self.commandHandler(line) |
|
else: |
|
self.connected = False |
|
|
|
if self.connected: |
|
nick = self.nickname |
|
lines = ["NOTICE AUTH :*** Looking up your hostname...", |
|
"NOTICE AUTH :*** Found your hostname, welcome back", |
|
"NOTICE AUTH :*** Checking ident", |
|
"NOTICE AUTH :*** No identd (auth) response", |
|
":%s 001 %s :Welcome to xmpp-ircd, IRC to XMPP gateway %s!%s" % |
|
(self.server, nick, nick, self.makeHostFromJID(self.JID)), |
|
":%s 002 %s :Your host is %s [%s port %s] running version xmpp-ircd-%s" % ( |
|
self.server, |
|
nick, |
|
self.server, |
|
self.server, |
|
self.port, |
|
XMPPIRCDVERSION), |
|
":%s 003 %s :This server was created %s" % (self.server, nick, self.component.startup_time), |
|
":%s 004 %s :%s xmpp-ircd%s spmAFkPBaTuUovbn q" % (self.server, nick, self.server, XMPPIRCDVERSION) |
|
] |
|
while lines: |
|
self.sendToIRC(lines.pop(0)) |
|
|
|
"""Here is this threads main functionality. Jabber-thread is started |
|
and polling of socket for IRC-messages is in here.""" |
|
jt = self.component.jt |
|
|
|
while self.connected and jt.connected: |
|
try: |
|
data = self.socket.recv(4096) |
|
except: |
|
self.printError('Not receiving enough data from socket') |
|
self.connected = False |
|
if data.find ( 'PING' ) != -1: |
|
# check that our rooms are still alive |
|
if self.pingCounter == 5: |
|
self.pingCounter = 0 |
|
for muc in self.mucs.keys(): |
|
if self.disconnectedMucs.has_key(muc): |
|
if self.disconnectedMucs[muc] < 5: |
|
self.disconnectedMucs[muc] = self.disconnectedMucs[muc] + 1 |
|
else: |
|
self.disconnectedMucs[muc] = 0 |
|
self.roomPingQueue[muc] = '' |
|
self.xmppCommandMUCMODE(muc) |
|
else: |
|
self.roomPingQueue[muc] = '' |
|
self.xmppCommandMUCMODE(muc) |
|
else: |
|
self.pingCounter += 1 |
|
self.sendToIRC('PONG %s' % (self.server)) |
|
if data: |
|
for line in data.splitlines(): |
|
self.commandHandler(line) |
|
else: |
|
self.connected = False |
|
if jt.connected: |
|
# leave all rooms |
|
for room in self.mucs.keys(): |
|
self.sendToXMPP(Presence(to='%s/%s' % (room, self.nickname), |
|
typ='unavailable', |
|
status='')) |
|
else: |
|
self.ircCommandNOTICE('XMPP server disconnected, shutting down xmpp-ircd.') |
|
self.component.unregisterJid(self) |
|
try: |
|
self.socket.shutdown(socket.SHUT_RDWR) |
|
except socket.error: |
|
self.printError('Socket shutdown client') |
|
self.socket.close() |
|
|
|
def messageHandlerError(self, sess, mess): |
|
"""Handle incoming error messages from XMPP |
|
|
|
@type sess: string |
|
@type mess: Message |
|
@param sess: session |
|
@param mess: error message |
|
|
|
""" |
|
text = '' |
|
try: |
|
text = mess.getTag('error').getTag('text').getData() |
|
except: |
|
pass |
|
erc = mess.getErrorCode() |
|
jidFrom = mess.getFrom() |
|
to = mess.getTo() |
|
if erc == '403': |
|
self.ircCommandERRORMUC(482, text, jidFrom) |
|
else: |
|
self.printDebug('MUC ERROR NOT IMPLEMENTED') |
|
|
|
def messageHandler(self, sess, mess): |
|
"""Handle incoming XMPP with type message |
|
|
|
@type sess: string |
|
@type mess: Message |
|
@param sess: session |
|
@param mess: XMPP Message |
|
""" |
|
if mess.getType() == 'error': |
|
self.messageHandlerError(sess,mess) |
|
return |
|
|
|
jid = mess.getFrom() |
|
text = mess.getBody() |
|
topic = mess.getSubject() |
|
|
|
ts = '' |
|
if mess.getTag('x',namespace=NS_DELAY): |
|
ts=mess.getTimestamp() |
|
if not ts: |
|
ts=mess.setTimestamp() |
|
ts=mess.getTimestamp() |
|
ts = time.strptime(ts,'%Y%m%dT%H:%M:%S') |
|
ts = datetime.datetime(*ts[:-3]) |
|
if not text and not topic: |
|
return |
|
|
|
private = True |
|
if mess.getType() == 'groupchat': |
|
private = False |
|
|
|
MUC = self.mucs.has_key(jid.getStripped()) |
|
|
|
if private: |
|
self.ircCommandPRIVMSG(jid, MUC, True, text, ts) |
|
elif topic: |
|
self.ircCommandTOPIC(jid, topic) |
|
elif not jid.getResource() == self.nickname or ts: |
|
self.ircCommandPRIVMSG(jid, True, False, text, ts) |
|
|
|
|
|
def iqHandler(self, con, iq): |
|
"""Handle incoming XMPP with type Iq |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
ns = iq.getQueryNS() |
|
if ns is None: |
|
ns = iq.getProperties()[0] |
|
|
|
if ns == NS_DISCO and iq.getType() in ['get', 'error']: |
|
self.iqHandlerInfo(con, iq) |
|
elif ns == NS_DISCO_ITEMS and iq.getType() == 'result': |
|
self.iqHandlerItems(con, iq) |
|
elif ns == NS_DISCO_INFO and iq.getType() == 'result': |
|
self.iqHandlerInfo(con, iq) |
|
elif ns == NS_DISCO_INFO and iq.getType() == 'get': |
|
self.xmppCommandINFOGET(iq.getFrom()) |
|
elif ns == NS_DISCO_ITEMS and iq.getType() == 'error': |
|
self.iqHandlerError(con, iq) |
|
elif ns == NS_DISCO_INFO and iq.getType() == 'error': |
|
self.iqHandlerError(con, iq) |
|
elif ns == NS_VCARD and iq.getType() == 'result': |
|
self.iqHandlerVcard(con, iq) |
|
elif ns == NS_VCARD and iq.getType() == 'error': |
|
self.iqHandlerVcardError(con, iq) |
|
elif ns == NS_LAST and iq.getType() == 'result': |
|
self.iqHandlerLast(con, iq) |
|
elif ns == NS_LAST and iq.getType() == 'get': |
|
self.xmppCommandLASTACTIVITY(iq.getFrom()) |
|
elif ns == NS_LAST and iq.getType() == 'error': |
|
self.iqHandlerLastError(con, iq) |
|
elif ns == NS_VERSION and iq.getType() == 'result': |
|
self.iqHandlerVersion(con, iq) |
|
elif ns == NS_VERSION and iq.getType() == 'error': |
|
self.iqHandlerVersionError(con, iq) |
|
elif ns == NS_VERSION and iq.getType() == 'get': |
|
self.xmppCommandSOFTWAREVERSION(iq.getFrom()) |
|
else: |
|
self.printDebug('IQ HANDLER FOR THIS NAMESPACE NOT IMPLEMENTED YET') |
|
|
|
|
|
def iqHandlerLastError(self,con, iq): |
|
"""Handle incoming XMPP with type Iq and last activity error |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
self.printDebug('iqHandlerLastError') |
|
|
|
def iqHandlerLast(self, con, iq): |
|
"""Handle incoming XMPP with type Iq and last activity |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
ch = iq.getTag('query') |
|
seconds = ch.getAttr('seconds') |
|
self.ircCommandNOTICE('** Last active information for %s **' % iq.getFrom()) |
|
self.ircCommandNOTICE('Idle %s second**' % seconds) |
|
|
|
def iqHandlerVersionError(self,con, iq): |
|
"""Handle incoming XMPP with type Iq and version error |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
self.printDebug('iqHandlerVersionError') |
|
|
|
def iqHandlerVersion(self, con, iq): |
|
"""Handle incoming XMPP with type Iq and version |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
ch = iq.getTag('query').getChildren() |
|
self.ircCommandNOTICE('** Software version information for %s **' % iq.getFrom()) |
|
for c in ch: |
|
self.ircCommandNOTICE('%s: %s' % (c.getName(), c.getData())) |
|
|
|
def iqHandlerVcardError(self,con, iq): |
|
"""Handle incoming XMPP with type Iq and vcard error |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
self.printDebug('iqHandlerVcardError') |
|
|
|
def iqHandlerVcard(self, con, iq): |
|
"""Handle incoming XMPP with type Iq and vcard |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
card = {} |
|
ch = iq.getTag('vCard').getChildren() |
|
for c in ch: |
|
name = c.getName() |
|
if name != 'PHOTO': |
|
if name == 'EMAIL': |
|
emailv = c.getChildren() |
|
card['EMAIL %s' % emailv[0].getName()] = emailv[1].getData() |
|
card[c.getName()] = c.getData() |
|
|
|
self.ircCommandNOTICE('** Vcard information for %s **' % iq.getFrom()) |
|
for key in card.keys(): |
|
for line in card[key].splitlines(): |
|
self.ircCommandNOTICE('%s: %s' % (key, line)) |
|
|
|
def iqHandlerError(self, con , iq): |
|
"""Handle incoming XMPP with type Iq and error |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
jid = iq.getFrom() |
|
# this can mess things up, fix later |
|
if self.roomPingQueue.has_key(jid): |
|
del (self.roomPingQueue[jid]) |
|
# room errors |
|
errornum = iq.getErrorCode() |
|
if jid in self.mucs.keys(): |
|
if errornum == '404' and jid not in self.disconnectedMucs.keys(): |
|
self.ircCommandERRORMUC(404, 'MUC DISCONNECTED', jid) |
|
self.ircCommandPRIVMSG(JID("%s/%s" % (jid, 'telepaatti')), |
|
True, |
|
False, |
|
'MUC IS DISCONNECTED YOUR TEXT WILL NOT SHOW ON CHANNEL. YOU CAN WAIT UNTIL MUC CONNECTS AGAIN OR USE /PART TO LEAVE THIS MUC!', |
|
timestamp='') |
|
self.disconnectedMucs[jid] = 0 |
|
return |
|
else: |
|
return |
|
else: |
|
self.ircCommandERROR('iq error num %s jid not room! jid %s' % (errornum, jid)) |
|
|
|
|
|
def iqHandlerItems(self, con, iq): |
|
"""Handle incoming XMPP with type Iq and items |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
|
|
jid = iq.getFrom() |
|
if iq.getType() == 'error': |
|
# some fixing is needed |
|
self.ircCommandERROR('%s %s ' % (iq.getErrorCode(), iq.getErrorCode())) |
|
elif iq.getID() == 'disco_muc_users': |
|
ch = iq.getQueryChildren() |
|
mucusers = list() |
|
for c in ch: |
|
name = c.getName() |
|
if name == 'item': |
|
mucusers.append(JID(c.getAttrs()['jid'])) |
|
if self.mucs.has_key(jid): |
|
pass # we keep track of users else where |
|
self.ircCommandWHO(mucusers, jid) |
|
return |
|
elif iq.getID() == 'disco_muc_rooms': |
|
ch = iq.getQueryChildren() |
|
channels = list() |
|
for c in ch: |
|
name = c.getName() |
|
if name == 'item': |
|
channels.append(self.fixChannel(c.getAttrs()['jid'])) |
|
self.ircCommandLIST(channels) |
|
return |
|
else: |
|
self.printDebug('UNKNOWN DISCO ITEM %s ' % jid) |
|
|
|
def iqHandlerInfo(self, con, iq): |
|
"""Handle incoming XMPP with type Iq and info |
|
|
|
@type con: Connection |
|
@type iq: Iq |
|
@param con: XMPP Connection |
|
@param iq: XMPP Iq |
|
""" |
|
roomname = iq.getFrom() |
|
# this can mess things up, fix |
|
if self.roomPingQueue.has_key(roomname): |
|
del (self.roomPingQueue[roomname]) |
|
return |
|
|
|
MUC = False |
|
roomfeats = list() |
|
if iq.getType() == 'error': |
|
# fix this later |
|
self.ircCommandERROR('%s %s ' % (iq.getErrorCode(), iq.getErrorCode())) |
|
else: |
|
ch = iq.getQueryChildren() |
|
for c in ch: |
|
name = c.getName() |
|
if name == 'identity': |
|
atrs = c.getAttrs() |
|
if atrs.has_key('type') and atrs.has_key('category'): |
|
if atrs['type'] == 'text' and \ |
|
atrs['category'] == 'conference': |
|
MUC = True |
|
elif name == 'var': |
|
roomfeats.append(c.getAttrs()['var']) |
|
elif name == 'feature': |
|
roomfeats.append(c.getAttrs()['var']) |
|
else: |
|
self.printDebug('%s NOT IMPLeMENTED' % name) |
|
if MUC: # for MODE |
|
modestr = '+' |
|
for feat in roomfeats: |
|
if feat == 'muc_hidden': |
|
modestr += 's' |
|
if feat == 'muc_membersonly': |
|
modestr += 'p' |
|
if feat == 'muc_moderated': |
|
modestr += 'm' |
|
if feat == 'muc_nonanonymous': |
|
modestr += 'A' |
|
if feat == 'muc_open': |
|
modestr += 'F' |
|
if feat == 'muc_passwordprotected': |
|
modestr += 'k' |
|
if feat == 'muc_persistent': |
|
modestr += 'P' |
|
if feat == 'muc_public': |
|
modestr += 'B' |
|
if feat == 'muc_rooms': |
|
self.printDebug('muc_rooms not implemented') |
|
if feat == 'muc_semianonymous': |
|
modestr += 'a' |
|
if feat == 'muc_temporary': |
|
modestr += 'T' |
|
if feat == 'muc_unmoderated': |
|
modestr += 'u' |
|
if feat == 'muc_unsecured': |
|
modestr += 'U' |
|
self.ircCommandMODEMUC(roomname, modestr) |
|
else: |
|
self.printDebug("IQ stuff still missing here") |
|
|
|
def presenceHandler(self, sess, pres): |
|
"""Handle incoming XMPP with type presence |
|
|
|
@type sess: Connection |
|
@type pres: Presence |
|
@param sess: XMPP Connection |
|
@param press: XMPP Presence |
|
""" |
|
MUC = False |
|
ptype = pres.getType() |
|
nick=pres.getFrom() |
|
tags = pres.getTags('x') |
|
for tag in tags: |
|
ns = tag.getNamespace() |
|
if ns.startswith(NS_MUC): |
|
MUC = True |
|
|
|
if not MUC: |
|
self.printDebug('non-muc presence somehow? investigate...') |
|
return |
|
|
|
role = pres.getRole() |
|
affiliation = pres.getAffiliation() |
|
show = pres.getShow() |
|
status = pres.getStatus() |
|
|
|
room = JID(pres.getFrom().getStripped()) |
|
|
|
# for affiliation and role changes |
|
if self.mucs.has_key(room) and \ |
|
nick in self.mucs[room].keys(): |
|
xrole = self.mucs[room][nick]['role'] |
|
xaffiliation = self.mucs[room][nick]['affiliation'] |
|
if role != xrole: # role has changed |
|
giver = JID('%s/telepaatti' % room) |
|
if role.upper() == 'MODERATOR': |
|
self.ircCommandMODEMUCUSER(giver, nick, '+o') |
|
self.ircCommandMODEMUCUSER(giver, nick, '-v') |
|
if role.upper() == 'PARTICIPANT': |
|
self.ircCommandMODEMUCUSER(giver, nick, '-o') |
|
self.ircCommandMODEMUCUSER(giver, nick, '+v') |
|
if role.upper() == 'VISITOR': |
|
self.ircCommandMODEMUCUSER(giver, nick, '-o') |
|
self.ircCommandMODEMUCUSER(giver, nick, '-v') |
|
else: |
|
self.printDebug('MODE NONE') |
|
if xaffiliation != affiliation: # affiliation has changed |
|
pass |
|
|
|
# for nick changes |
|
if (pres.getNick() == self.newnick or pres.getNick() == self.nickname)\ |
|
and pres.getStatusCode() == '303': |
|
self.nickChangeInMucs[room] = {'checked': True, |
|
'changed': True} |
|
# check if we have checked all MUCs |
|
for muc in self.nickChangeInMucs.keys(): |
|
if not self.nickChangeInMucs[muc]['checked']: |
|
return # no need to go any further |
|
# check if it changed in all MUCs |
|
for muc in self.nickChangeInMucs.keys(): |
|
if not self.nickChangeInMucs[muc]['changed']: |
|
changedMucs = list() |
|
for gei in self.nickChangeInMucs.keys(): |
|
if self.nickChangeInMucs[gei]['changed']: |
|
changedMucs.append(gei) |
|
self.nickChangeInMucs = {} |
|
for muc2 in changedMucs: |
|
self.nickChangeInMucs[muc2] = {'checked': False, |
|
'changed': False} |
|
self.xmppCommandMUCPRESENCE(muc2, self.nickname) |
|
self.ircCommandERROR('Nick conflicts in some MUC wont change') |
|
return # out |
|
# remove, all have changed |
|
self.nickChangeInMucs = {} |
|
if pres.getNick() == self.nickname: |
|
self.newnick = '' |
|
return |
|
self.sendToIRC(':%s NICK :%s' % |
|
(self.nickname, |
|
self.newnick)) |
|
for muc in self.getMucs(): |
|
del (self.mucs[muc][JID("%s/%s" % (muc, self.nickname))]) # remove the old |
|
# add the new |
|
self.mucs[muc][JID("%s/%s" % (muc, self.newnick))] = { 'role': role, |
|
'affiliation': affiliation } |
|
self.nickname = self.newnick |
|
self.newnick = '' |
|
return |
|
|
|
if ptype == 'error': |
|
er = pres.getError() |
|
erc = pres.getErrorCode() |
|
if erc == '401': |
|
self.ircCommandERRORMUC(475, 'Password requeired to join', room) |
|
elif erc == '403': |
|
self.ircCommandERRORMUC(474, 'Cannot join MUC, you are banned', room) |
|
elif erc == '404': |
|
self.ircCommandERRORMUC(404, 'No such MUC', room) |
|
elif erc == '405': |
|
self.ircCommandERRORMUC(478, 'Can\'t create MUC', room) |
|
elif erc == '406': |
|
self.ircCommandERRORMUC(437, 'You must use reserverd nick to enter', room) |
|
elif erc == '407': |
|
self.ircCommandERRORMUC(473, 'Must be a member to enter', room) |
|
elif erc == '409': |
|
self.ircCommandERRORMUC(437, 'You must change nickname to enter', room) |
|
elif erc == '503': |
|
self.ircCommandERRORMUC(471, 'MUC is full', room) |
|
else: |
|
self.ircCommandERROR('MUC error not yet implemented (%d %s)' % (erc, er)) |
|
else: |
|
joining = self.joinQueue.has_key(room) |
|
inroom = self.mucs.has_key(room) |
|
if ptype == 'unavailable': |
|
self.printDebug('unavailable') |
|
if nick.getResource() == self.nickname: |
|
self.printDebug('our self') |
|
if joining: |
|
del (self.joinQueue[room]) |
|
elif self.nickChangeInMucs.has_key(room): |
|
# between nick change |
|
self.printDebug('we are between nick change') |
|
return |
|
elif inroom: |
|
self.ircCommandPART(nick, ' left') |
|
if nick in self.mucs[room].keys(): |
|
del (self.mucs[room]) |
|
else: |
|
line = "%s is doing something" % nick |
|
self.printDebug(line.encode('utf-8')) |
|
else: # someonerin else |
|
if joining: |
|
self.printDebug("%s left while we are joining room %s" % ( |
|
nick, room)) |
|
elif inroom: |
|
if pres.getStatusCode() == '303': |
|
self.changingNick[JID("%s/%s" % (nick.getStripped(), pres.getNick()))] = nick |
|
else: |
|
self.ircCommandPART(nick, 'left') |
|
|
|
del (self.mucs[room][nick]) |
|
else: |
|
line = "%s is doing something" % nick |
|
self.printDebug(line.encode('utf-8')) |
|
else: # not unavailable type |
|
self.printDebug('not unavailable') |
|
if nick.getResource() == self.nickname: |
|
if joining: |
|
# fix this also later |
|
self.mucs[room] = self.joinQueue[room]['users'] |
|
self.mucs[room][JID("%s/%s" % (room, self.nickname))] = { 'role': role, |
|
'affiliation': affiliation, |
|
'show' : show, |
|
'status': status} |
|
del(self.joinQueue[room]) |
|
self.ircCommandSELFJOIN(room) |
|
elif inroom: |
|
self.mucs[room][JID("%s/%s" % (room, self.nickname))] = { 'role': role, |
|
'affiliation': affiliation, |
|
'show' : show, |
|
'status': status} |
|
else: |
|
line = "%s is doing something" % nick |
|
self.printDebug(line.encode('utf-8')) |
|
elif nick.getResource() == self.newnick: |
|
pass |
|
else: # someone else |
|
if joining: |
|
if nick not in self.joinQueue[room]['users'].keys(): |
|
self.joinQueue[room]['users'][nick] = { 'role': role, |
|
'affiliation': affiliation, |
|
'show' : show, |
|
'status': status} |
|
elif inroom: |
|
new_user = nick not in self.mucs[room].keys() |
|
self.mucs[room][nick] = { 'role': role, |
|
'affiliation': affiliation, |
|
'show' : show, |
|
'status': status } |
|
if nick in self.changingNick.keys(): |
|
self.ircCommandNICK(self.changingNick[nick], nick) |
|
elif new_user: |
|
self.ircCommandJOIN(nick) |
|
else: |
|
self.printDebug('TROUBLE LINE') |
|
|
|
def commandHandler(self, data): |
|
"""Command handler for commands and text coming in from IRC-client |
|
|
|
@type data: string |
|
@param data: IRC data coming from IRC-client |
|
""" |
|
self.printDebug('got ircline: %s' % data) |
|
# utf-8 test |
|
try: |
|
unicode(data, 'utf-8') |
|
except exceptions.UnicodeDecodeError: |
|
self.printError('Unicode decode error. Your IRC client is (probably) not writing utf-8') |
|
self.ircCommandERROR('Input form IRC client was not in utf-8. Turn utf-8 support on from your IRC client or input only pure ascii',-1) |
|
return |
|
|
|
args = data.split(' ', 1) |
|
arguments = u'' |
|
command = args[0].upper() |
|
if len(args) == 2: |
|
arguments = args[1] |
|
arguments = arguments.strip() |
|
MUC = arguments.startswith('#') |
|
if MUC: |
|
arguments = self.fixChannelCommand(arguments) |
|
|
|
if self.nickname is None: |
|
if command == 'NICK': |
|
nick = '' |
|
if arguments[0] == ':': |
|
nick = arguments[1:] |
|
else: |
|
nick = arguments |
|
|
|
self.nickname = self.fixNick(nick) |
|
|
|
elif command == 'PASS': |
|
if arguments[0] == ':': |
|
self.passwd = arguments[1:] |
|
else: |
|
self.passwd = arguments |
|
|
|
return |
|
|
|
if command == 'JOIN': |
|
#We won't be able to join rooms with a space in their name, but it's not as bad as being unable to join rooms with a password |
|
arguments = arguments.split(' ', 1) |
|
room = arguments[0] |
|
password = u'' |
|
if len(arguments) == 2: |
|
password = arguments[1] |
|
|
|
if self.fullRoomJid and not room.endswith("@%s" % self.muc_server): |
|
room = "%s@%s" % (room, self.muc_server) |
|
|
|
room = room.lower() # todo: is this valid? |
|
if room in self.mucs.keys(): # already in MUC |
|
return |
|
self.printDebug("Joining room: %s" % JID(room)) |
|
self.joinQueue[JID(room)] = {'messages': list(), |
|
'users': {}} |
|
p=Presence(to='%s/%s' % ( |
|
room, |
|
self.nickname)) |
|
p.setTag('x',namespace=NS_MUC).setTagData('password', password) |
|
p.getTag('x').addChild('history',{'maxchars':'10000','maxstanzas':'100'}) |
|
self.sendToXMPP(p) |
|
|
|
elif command == 'PART': |
|
x = arguments.find(' :') |
|
text = '' |
|
room = '' |
|
if x > 0: |
|
text = arguments[x+2:] |
|
text = text.strip() |
|
room = arguments[:x] |
|
room = JID(room.strip()) |
|
else: |
|
room = JID(arguments.strip()) |
|
if room not in self.mucs.keys(): # not in room |
|
return |
|
self.sendToXMPP(Presence(to='%s/%s' % (room, self.nickname), |
|
typ='unavailable', |
|
status=text)) |
|
|
|
elif command == 'PRIVMSG': |
|
x = arguments.find(' :') |
|
text = '' |
|
nick = '' |
|
if x > 0: |
|
text = arguments[x+2:] |
|
text = text.strip() |
|
sact = text.find('\001ACTION ') |
|
eact = text.rfind('\001') |
|
if sact > -1 and eact > -1: |
|
text = '/me %s' % text[sact+8:eact] |
|
nick = arguments[:x] |
|
nick = nick.strip() |
|
type = 'chat' |
|
if MUC: |
|
type = 'groupchat' |
|
|
|
jid = self.getJIDFromNick(nick) |
|
if jid is None: |
|
return |
|
|
|
self.sendToXMPP(protocol.Message(jid, |
|
text, |
|
typ = type)) |
|
|
|
elif command == 'NICK': |
|
if arguments[0] == ':': |
|
self.newnick = self.fixNick(arguments[1:]) |
|
else: |
|
self.newnick = self.fixNick(arguments) |
|
|
|
if self.newnick == self.nickname: |
|
self.newnick = '' |
|
return |
|
|
|
if len(self.getMucs()) == 0: |
|
self.sendToIRC(':%s NICK :%s' % |
|
(self.nickname, |
|
self.newnick)) |
|
self.nickname=self.newnick |
|
self.newnick = '' |
|
|
|
for muc in self.getMucs(): |
|
self.nickChangeInMucs[muc] = {'checked': False, |
|
'changed': False} |
|
for muc in self.nickChangeInMucs.keys(): |
|
self.xmppCommandMUCPRESENCE(muc, self.newnick) |
|
|
|
elif command == 'TOPIC': |
|
x = arguments.find(' :') |
|
text = '' |
|
jid = None |
|
if x > 0: |
|
text = arguments[x+2:] |
|
text = text.strip() |
|
jid = JID(arguments[:x].strip()) |
|
if jid not in self.mucs.keys(): |
|
self.ircCommandERROR('', 403) |
|
return |
|
|
|
self.sendToXMPP(protocol.Message(jid, |
|
typ = 'groupchat', |
|
subject = text)) |
|
|
|
elif command == 'MODE': |
|
if not arguments: |
|
return |
|
arguments = arguments.split(' ', 2) |
|
params = '' |
|
nick = arguments[0] |
|
|
|
if len(arguments) >= 2: |
|
params = arguments[1] |
|
if len(arguments) == 3: |
|
tonick = params |
|
params = arguments[2] |
|
|
|
if nick == self.nickname: |
|
self.ircCommandMODE(params) |
|
else: |
|
jid = self.getJIDFromNick(nick) |
|
if jid is None: |
|
return |
|
if params.find('b') > -1: # get bandlist |
|
self.ircCommandMODEMUCBANLIST(jid) |
|
return |
|
elif params.find('+o') > -1: # trying to op someone |
|
self.xmppCommandMUCROLE(jid, tonick, 'moderator') |
|
elif params.find('-o') > -1: # trying to deop someone |
|
self.xmppCommandMUCROLE(jid, tonick, 'participant') |
|
elif params.find('+v') > -1: # trying to voice someone |
|
self.xmppCommandMUCROLE(jid, tonick, 'participant') |
|
elif params.find('-v') > -1: # trying to voice someone |
|
self.xmppCommandMUCROLE(jid, tonick, 'visitor') |
|
else: |
|
self.xmppCommandMUCMODE(jid) |
|
|
|
elif command == 'WHO': |
|
if not arguments: |
|
return |
|
jid = JID(arguments) |
|
self.xmppCommandMUCUSERS(jid) |
|
|
|
elif command == 'WHOIS': |
|
jid = self.getJIDFromNick(arguments) |
|
if jid is None: |
|
return |
|
self.ircCommandWHOIS(jid) |
|
self.xmppCommandGETWHOIS(jid) |
|
|
|
elif command == 'AWAY': |
|
# FIXME <https://github.com/moparisthebest/xmpp-ircd/issues/2> |
|
self.printError('AWAY command ignored to avoid crashing (FIXME): %s' % data) |
|
return |
|
|
|
arguments = arguments[1:] # remove the : |
|
show = '' |
|
if arguments != '': |
|
show = 'away' |
|
args = arguments.split(' ',1) |
|
status = arguments |
|
if args[0].upper() in STATUSSTATES: |
|
show = args[0] |
|
if len(args) == 2: |
|
status = args[1] |
|
self.xmppCommandSTATUS(show, status) |
|
|
|
elif command == 'LIST': |
|
# https://tools.ietf.org/html/rfc1459#section-4.2.6 |
|
# todo: handle list,of,channel,args? |
|
self.xmppCommandMUCROOMS() |
|
|
|
elif command == 'QUIT': |
|
self.connected = False |
|
|
|
else: |
|
self.printError('ircline not handled: %s' % data) |
|
|
|
def usage(): |
|
"""Usage function for showing commandline options """ |
|
print "Usage: xmpp-ircd [OPTION]..." |
|
print "OPTIONS" |
|
print "-h, --help\t help" |
|
print "-p, --port\t port to listen for IRC connections on" |
|
print "-m, --muc-server\t Address of the MUC service. Used for autocompletion of JOIN commands" |
|
print "-s, --server\t Jabber/XMPP server to which the component connection should be made" |
|
print "-P, --server-port\t Port to which the component connection should be made" |
|
print "-c, --component-name\t Name of component" |
|
print "-C, --component-pass\t Component password" |
|
print " --ssl\t SSL certificate. Enables ssl when provided" |
|
print " --dh\t Diffie Hellman parameter file for SSL." |
|
print " --log\t log file" |
|
|
|
def main(): |
|
port = 6667 |
|
server = '127.0.0.1' |
|
server_port = 5347 |
|
muc_server = None |
|
component_name = None |
|
component_pass = None |
|
ssl_cert = None |
|
dh_param = None |
|
daemonize = False |
|
log_file = '/var/log/xmpp-ircd' |
|
|
|
try: |
|
opts, args = getopt.getopt(sys.argv[1:], "s:P:m:p:h:d:c:C:", ["server=","server-port=","muc-server=","port=","help","daemonize","ssl=","dh=","component-name=","component-pass="]) |
|
except getopt.GetoptError: |
|
usage() |
|
sys.exit(2) |
|
if len(opts) == 0: |
|
usage() |
|
sys.exit(2) |
|
|
|
for o, a in opts: |
|
if o in ("-h", "--help"): |
|
usage() |
|
sys.exit() |
|
if o in ("-p", "--port"): |
|
try: |
|
port = int(a) |
|
except: |
|
print "port should be an integer" |
|
sys.exit() |
|
if o in ("-P", "--server-port"): |
|
try: |
|
server_port = int(a) |
|
except: |
|
print "server-port should be an integer" |
|
sys.exit() |
|
if o in ("-s", "--server"): |
|
server = a |
|
if o in ("-m", "--muc-server"): |
|
muc_server = a |
|
if o in ("-c", "--component-name"): |
|
component_name = a |
|
if o in ("-C", "--component-pass"): |
|
component_pass = a |
|
if o in ("-d", "--daemonize"): |
|
daemonize = True |
|
if o == "--ssl": |
|
ssl_cert = a |
|
if o == "--dh": |
|
dh_param = a |
|
if daemonize: |
|
with daemon.DaemonContext(): |
|
daemon_main(server, server_port, port, muc_server, component_name, component_pass, ssl_cert, dh_param) |
|
else: |
|
daemon_main(server, server_port, port, muc_server, component_name, component_pass, ssl_cert, dh_param) |
|
|
|
class XmppComponent(): |
|
"""Class for Jabber connection thread""" |
|
|
|
def __init__(self, client, logger): |
|
self.client = client |
|
self.logger = logger |
|
self.clients = {} |
|
|
|
self.xmppSem = BoundedSemaphore(value=1) |
|
|
|
self.startup_time = datetime.datetime.now().strftime("%c") |
|
|
|
client.RegisterHandler('message', self.messageHandler) |
|
client.RegisterHandler('presence', self.presenceHandler) |
|
client.RegisterHandler('iq', self.iqHandler) |
|
|
|
self.jt = JabberThread(client) |
|
self.jt.start() |
|
|
|
# https://tools.ietf.org/html/rfc6122#section-2.3 |
|
def randomLocalpart(self, size=20, chars=string.ascii_lowercase + string.digits): |
|
return ''.join(random.choice(chars) for _ in range(size)) |
|
|
|
def registerJid(self, irc_client): |
|
nick = self.randomLocalpart() |
|
bare_jid = "%s@%s" %(nick, irc_client.server) |
|
#full_jid = "%s@%s/%s" %(nick, irc_client.server, 'telepaatti') |
|
while bare_jid in self.clients: |
|
# generate new random until we come across an unused one |
|
nick = self.randomLocalpart() |
|
bare_jid = "%s@%s" %(nick, irc_client.server) |
|
|
|
irc_client.bare_jid = bare_jid |
|
irc_client.JID = JID(bare_jid) |
|
#irc_client.printDebug("adding jid to clients: (full: %s) (bare: %s)" % (full_jid, bare_jid)) |
|
irc_client.printDebug("adding jid to clients: %s" % (bare_jid)) |
|
#self.clients[full_jid] = self |
|
self.clients[bare_jid] = irc_client |
|
|
|
def unregisterJid(self, irc_client): |
|
if irc_client.bare_jid in self.clients: |
|
del (self.clients[irc_client.bare_jid]) |
|
|
|
def send(self, msg): |
|
"""Sends message XMPP server |
|
|
|
@type msg: string |
|
@param msg: message to send |
|
""" |
|
self.xmppSem.acquire() |
|
self.client.send(msg) |
|
self.xmppSem.release() |
|
|
|
def messageHandler(self, sess, mess): |
|
self.logger.info("in messageHandler") |
|
try: |
|
jid = mess.getTo() |
|
self.logger.info("jid %s, clients: %s" % (jid, self.clients)) |
|
self.clients[jid].messageHandler(sess, mess) |
|
except: |
|
self.logger.error("Unexpected error: %s" % sys.exc_info()[0]) |
|
pass |
|
|
|
|
|
def presenceHandler(self, sess, mess): |
|
self.logger.info("in presenceHandler") |
|
try: |
|
jid = mess.getTo() |
|
self.logger.info("jid %s, clients: %s" % (jid, self.clients)) |
|
self.clients[jid].presenceHandler(sess, mess) |
|
except: |
|
self.logger.error("Unexpected error: %s" % sys.exc_info()[0]) |
|
pass |
|
|
|
def iqHandler(self, sess, mess): |
|
self.logger.info("in iqHandler") |
|
try: |
|
jid = mess.getTo() |
|
self.logger.info("jid %s, clients: %s" % (jid, self.clients)) |
|
self.clients[jid].iqHandler(sess, mess) |
|
except: |
|
self.logger.error("Unexpected error: %s" % sys.exc_info()[0]) |
|
pass |
|
|
|
def daemon_main(server, server_port, port, muc_server, component_name, component_pass, ssl_cert, dh_param): |
|
service = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
service.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
|
service.bind(("", port)) |
|
service.listen(1) |
|
|
|
main_logger = logging.getLogger("main_logger") |
|
#main_logger.addHandler(logging.handlers.SysLogHandler(address = '/var/run/log')) |
|
main_logger.addHandler(logging.StreamHandler()) |
|
main_logger.setLevel(10) |
|
|
|
main_logger.info("listening on port %s" % (port)) |
|
|
|
ssl_ctx = None |
|
if ssl_cert is not None: |
|
main_logger.info("Using ssl certificate %s" % (ssl_cert)) |
|
ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) |
|
ssl_ctx.set_ciphers("ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK") |
|
if dh_param is not None: |
|
main_logger.info("Using DH parameter %s" % (dh_param)) |
|
ssl_ctx.load_dh_params(dh_param) |
|
ssl_ctx.load_cert_chain(ssl_cert) |
|
|
|
client = Component(component_name, server_port) |
|
|
|
#client.connect(proxy={}) |
|
client.connect((server, server_port)) |
|
|
|
if not client.auth(component_name, component_pass): |
|
main_logger.error('auth failed component: %s pass: %s' % (component_name, component_pass)) |
|
return |
|
|
|
component = XmppComponent(client, main_logger) |
|
|
|
while (True): |
|
(clientsocket, address ) = service.accept() |
|
if ssl_ctx is not None: |
|
try: |
|
clientsocket = ssl_ctx.wrap_socket(clientsocket, server_side = True) |
|
except: |
|
main_logger.error('Failed SSL handshake: %s - %s' % (sys.exc_info()[0], sys.exc_info()[1])) |
|
try: |
|
clientsocket.shutdown(socket.SHUT_RDWR) |
|
except socket.error: |
|
main_logger.error('Failed socket shutdown') |
|
clientsocket.close() |
|
continue |
|
ct = ClientThread(clientsocket, port, component_name, muc_server, component) |
|
ct.start() |
|
|
|
if __name__ == "__main__": |
|
main()
|
|
|