diff --git a/src/common/connection.py b/src/common/connection.py index 82e20c313..7b08adcf8 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -25,148 +25,41 @@ ## GNU General Public License for more details. ## -# kind of events we can wait for an answer -VCARD_PUBLISHED = 'vcard_published' -VCARD_ARRIVED = 'vcard_arrived' import sys -import sha import os import time import sre import traceback -import select -import socket import random random.seed() + import signal import base64 if os.name != 'nt': signal.signal(signal.SIGPIPE, signal.SIG_DFL) -from calendar import timegm -from encodings.punycode import punycode_encode - import common.xmpp - from common import helpers from common import gajim from common import GnuPG -import socks5 + +from connection_handlers import * USE_GPG = GnuPG.USE_GPG from common import i18n _ = i18n._ -HAS_IDLE = True -try: - import common.idle as idle # when we launch gajim from sources -except: - try: - import idle # when Gajim is installed - except: - gajim.log.debug(_('Unable to load idle module')) - HAS_IDLE = False - -STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible'] - -distro_info = { - 'Arch Linux': '/etc/arch-release', - 'Aurox Linux': '/etc/aurox-release', - 'Conectiva Linux': '/etc/conectiva-release', - 'CRUX': '/usr/bin/crux', - 'Debian GNU/Linux': '/etc/debian_release', - 'Debian GNU/Linux': '/etc/debian_version', - 'Fedora Linux': '/etc/fedora-release', - 'Gentoo Linux': '/etc/gentoo-release', - 'Linux from Scratch': '/etc/lfs-release', - 'Mandrake Linux': '/etc/mandrake-release', - 'Slackware Linux': '/etc/slackware-release', - 'Slackware Linux': '/etc/slackware-version', - 'Solaris/Sparc': '/etc/release', - 'Source Mage': '/etc/sourcemage_version', - 'SUSE Linux': '/etc/SuSE-release', - 'Sun JDS': '/etc/sun-release', - 'PLD Linux': '/etc/pld-release', - 'Yellow Dog Linux': '/etc/yellowdog-release', - # many distros use the /etc/redhat-release for compatibility - # so Redhat is the last - 'Redhat Linux': '/etc/redhat-release' -} - -def get_os_info(): - if os.name == 'nt': - ver = os.sys.getwindowsversion() - ver_format = ver[3], ver[0], ver[1] - win_version = { - (1, 4, 0): '95', - (1, 4, 10): '98', - (1, 4, 90): 'ME', - (2, 4, 0): 'NT', - (2, 5, 0): '2000', - (2, 5, 1): 'XP', - (2, 5, 2): '2003' - } - if win_version.has_key(ver_format): - return 'Windows' + ' ' + win_version[ver_format] - else: - return 'Windows' - elif os.name == 'posix': - executable = 'lsb_release' - params = ' --id --codename --release --short' - full_path_to_executable = helpers.is_in_path(executable, return_abs_path = True) - if full_path_to_executable: - command = executable + params - child_stdin, child_stdout = os.popen2(command) - output = helpers.temp_failure_retry(child_stdout.readline).strip() - child_stdout.close() - child_stdin.close() - # some distros put n/a in places so remove them - pattern = sre.compile(r' n/a', sre.IGNORECASE) - output = sre.sub(pattern, '', output) - return output - - # lsb_release executable not available, so parse files - for distro_name in distro_info: - path_to_file = distro_info[distro_name] - if os.path.exists(path_to_file): - if os.access(path_to_file, os.X_OK): - # the file is executable (f.e. CRUX) - # yes, then run it and get the first line of output. - text = helpers.get_output_of_command(path_to_file)[0] - else: - fd = open(path_to_file) - text = fd.readline().strip() # get only first line - fd.close() - if path_to_file.endswith('version'): - # sourcemage_version has all the info we need - if not os.path.basename(path_to_file).startswith('sourcemage'): - text = distro_name + ' ' + text - elif path_to_file.endswith('aurox-release'): - # file doesn't have version - text = distro_name - elif path_to_file.endswith('lfs-release'): # file just has version - text = distro_name + ' ' + text - return text - - # our last chance, ask uname and strip it - uname_output = helpers.get_output_of_command('uname -a | cut -d" " -f1,3') - if uname_output is not None: - return uname_output[0] # only first line - return 'N/A' - -class Connection: +class Connection(ConnectionHandlers): '''Connection class''' def __init__(self, name): + ConnectionHandlers.__init__(self) self.name = name self.connected = 0 # offline - self.connection = None # xmpppy instance + self.connection = None # xmpppy ClientCommon instance # this property is used to prevent double connections - self.last_connection = None # last TcpClient instance + self.last_connection = None # last ClientCommon instance self.gpg = None - self.vcard_sha = None - self.vcard_shas = {} # sha of contacts self.status = '' self.old_show = '' # increase/decrease default timeout for server responses @@ -177,9 +70,8 @@ class Connection: self.new_account_info = None self.bookmarks = [] self.on_purpose = False - self.last_io = time.time() + self.last_io = gajim.idlequeue.current_time() self.last_sent = [] - self.files_props = {} self.last_history_line = {} self.password = gajim.config.get_per('accounts', name, 'password') self.server_resource = gajim.config.get_per('accounts', name, 'resource') @@ -190,36 +82,17 @@ class Connection: self.privacy_rules_supported = False # Do we continue connection when we get roster (send presence,get vcard...) self.continue_connect_info = None - # List of IDs we are waiting answers for {id: (type_of_request, data), } - self.awaiting_answers = {} - # List of IDs that will produce a timeout is answer doesn't arrive - # {time_of_the_timeout: (id, message to send to gui), } - self.awaiting_timeouts = {} if USE_GPG: self.gpg = GnuPG.GnuPG() gajim.config.set('usegpg', True) else: gajim.config.set('usegpg', False) - - try: - idle.init() - except: - HAS_IDLE = False + self.on_connect_success = None self.retrycount = 0 self.jids_for_auto_auth = [] # list of jid to auto-authorize - self.room_jids = [] # list of gc jids so that vcard are saved in a folder + # END __init__ - - def get_full_jid(self, iq_obj): - '''return the full jid (with resource) from an iq as unicode''' - return helpers.parse_jid(str(iq_obj.getFrom())) - - def get_jid(self, iq_obj): - '''return the jid (without resource) from an iq as unicode''' - jid = self.get_full_jid(iq_obj) - return gajim.get_jid_without_resource(jid) - def put_event(self, ev): if gajim.handlers.has_key(ev[0]): gajim.handlers[ev[0]](self.name, ev[1]) @@ -228,436 +101,6 @@ class Connection: '''always passes account name as first param''' self.put_event((event, data)) - def add_sha(self, p): - c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE) - if self.vcard_sha is not None: - c.setTagData('photo', self.vcard_sha) - return p - - # this is in features.py but it is blocking - def _discover(self, ns, jid, node = None): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = ns) - if node: - iq.setQuerynode(node) - self.connection.send(iq) - - def discoverItems(self, jid, node = None): - '''According to JEP-0030: jid is mandatory, - name, node, action is optional.''' - self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node) - - def discoverInfo(self, jid, node = None): - '''According to JEP-0030: - For identity: category, type is mandatory, name is optional. - For feature: var is mandatory''' - self._discover(common.xmpp.NS_DISCO_INFO, jid, node) - - def node_to_dict(self, node): - dict = {} - for info in node.getChildren(): - name = info.getName() - if name in ('ADR', 'TEL', 'EMAIL'): # we can have several - if not dict.has_key(name): - dict[name] = [] - entry = {} - for c in info.getChildren(): - entry[c.getName()] = c.getData() - dict[name].append(entry) - elif info.getChildren() == []: - dict[name] = info.getData() - else: - dict[name] = {} - for c in info.getChildren(): - dict[name][c.getName()] = c.getData() - return dict - - def save_vcard_to_hd(self, full_jid, card): - jid, nick = gajim.get_room_and_nick_from_fjid(full_jid) - nick = nick.replace('/', '_') - puny_jid = punycode_encode(jid) - path = os.path.join(gajim.VCARD_PATH, puny_jid) - if jid in self.room_jids: - # remove room_jid file if needed - if os.path.isfile(path): - os.remove(path) - # create folder if needed - if not os.path.isdir(path): - os.mkdir(path, 0700) - puny_nick = punycode_encode(nick) - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - else: - path_to_file = path - fil = open(path_to_file, 'w') - fil.write(str(card)) - fil.close() - - def _vCardCB(self, con, vc): - '''Called when we receive a vCard - Parse the vCard and send it to plugins''' - if not vc.getTag('vCard'): - return - frm_iq = vc.getFrom() - our_jid = gajim.get_jid_from_account(self.name) - resource = '' - if frm_iq: - who = self.get_full_jid(vc) - frm, resource = gajim.get_room_and_nick_from_fjid(who) - else: - who = frm = our_jid - if vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD: - card = vc.getChildren()[0] - vcard = self.node_to_dict(card) - photo_decoded = None - if vcard.has_key('PHOTO') and isinstance(vcard['PHOTO'], dict) and \ - vcard['PHOTO'].has_key('BINVAL'): - photo = vcard['PHOTO']['BINVAL'] - try: - photo_decoded = base64.decodestring(photo) - avatar_sha = sha.sha(photo_decoded).hexdigest() - except: - avatar_sha = '' - else: - avatar_sha = '' - - if avatar_sha: - card.getTag('PHOTO').setTagData('SHA', avatar_sha) - - # Save it to file - self.save_vcard_to_hd(who, card) - # Save the decoded avatar to a separate file too, and generate files for dbus notifications - puny_jid = punycode_encode(frm) - puny_nick = None - begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid) - if frm in self.room_jids: - puny_nick = punycode_encode(resource.replace('/', '_')) - # create folder if needed - if not os.path.isdir(begin_path): - os.mkdir(begin_path, 0700) - begin_path = os.path.join(begin_path, puny_nick) - if photo_decoded: - avatar_file = begin_path + '_notif_size_colored.png' - if frm == our_jid and avatar_sha != self.vcard_sha: - gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) - elif frm != our_jid and (not os.path.exists(avatar_file) or \ - not self.vcard_shas.has_key(frm) or \ - avatar_sha != self.vcard_shas[frm]): - gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick) - else: - for ext in ('.jpeg', '.png', '_notif_size_bw.png', - '_notif_size_colored.png'): - path = begin_path + ext - if os.path.isfile(path): - os.remove(path) - - if frm != our_jid: - if avatar_sha: - self.vcard_shas[frm] = avatar_sha - elif self.vcard_shas.has_key(frm): - del self.vcard_shas[frm] - - vcard['jid'] = frm - vcard['resource'] = resource - if frm == our_jid: - self.dispatch('MYVCARD', vcard) - # we re-send our presence with sha if has changed and if we are - # not invisible - if self.vcard_sha == avatar_sha: - return - self.vcard_sha = avatar_sha - if STATUS_LIST[self.connected] == 'invisible': - return - sshow = helpers.get_xmpp_show(STATUS_LIST[self.connected]) - prio = unicode(gajim.config.get_per('accounts', self.name, - 'priority')) - p = common.xmpp.Presence(typ = None, priority = prio, show = sshow, - status = self.status) - p = self.add_sha(p) - self.connection.send(p) - else: - self.dispatch('VCARD', vcard) - - def _gMailNewMailCB(self, con, gm): - '''Called when we get notified of new mail messages in gmail account''' - if not gm.getTag('new-mail'): - return - if gm.getTag('new-mail').getNamespace() == common.xmpp.NS_GMAILNOTIFY: - # we'll now ask the server for the exact number of new messages - jid = gajim.get_jid_from_account(self.name) - gajim.log.debug('Got notification of new gmail e-mail on %s. Asking the server for more info.' % jid) - iq = common.xmpp.Iq(typ = 'get') - iq.setAttr('id', '13') - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_GMAILNOTIFY) - self.connection.send(iq) - raise common.xmpp.NodeProcessed - - def _gMailQueryCB(self, con, gm): - '''Called when we receive results from Querying the server for mail messages in gmail account''' - if not gm.getTag('mailbox'): - return - if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY: - newmsgs = gm.getTag('mailbox').getAttr('total-matched') - if newmsgs != '0': - # there are new messages - jid = gajim.get_jid_from_account(self.name) - gajim.log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid)) - self.dispatch('GMAIL_NOTIFY', (jid, newmsgs)) - raise common.xmpp.NodeProcessed - - def _messageCB(self, con, msg): - '''Called when we receive a message''' - msgtxt = msg.getBody() - mtype = msg.getType() - subject = msg.getSubject() # if not there, it's None - tim = msg.getTimestamp() - tim = time.strptime(tim, '%Y%m%dT%H:%M:%S') - tim = time.localtime(timegm(tim)) - frm = self.get_full_jid(msg) - jid = self.get_jid(msg) - no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for') - encrypted = False - chatstate = None - encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) - decmsg = '' - # invitations - invite = None - if not encTag: - invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) - if invite and not invite.getTag('invite'): - invite = None - delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None - msg_id = None - composing_jep = None - # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED - # invitation - # stanza (MUC JEP) remove in 2007, as we do not do NOT RECOMMENDED - xtags = msg.getTags('x') - for xtag in xtags: - if xtag.getNamespace() == common.xmpp.NS_CONFERENCE and not invite: - room_jid = xtag.getAttr('jid') - self.dispatch('GC_INVITATION', (room_jid, frm, '', None)) - return - # chatstates - look for chatstate tags in a message if not delayed - if not delayed: - composing_jep = False - children = msg.getChildren() - for child in children: - if child.getNamespace() == 'http://jabber.org/protocol/chatstates': - chatstate = child.getName() - composing_jep = 'JEP-0085' - break - # No JEP-0085 support, fallback to JEP-0022 - if not chatstate: - chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT) - if chatstate_child: - chatstate = 'active' - composing_jep = 'JEP-0022' - if not msgtxt and chatstate_child.getTag('composing'): - chatstate = 'composing' - - if encTag and USE_GPG: - #decrypt - encmsg = encTag.getData() - - keyID = gajim.config.get_per('accounts', self.name, 'keyid') - if keyID: - decmsg = self.gpg.decrypt(encmsg, keyID) - if decmsg: - msgtxt = decmsg - encrypted = True - if mtype == 'error': - self.dispatch('MSGERROR', (frm, msg.getErrorCode(), msg.getError(), - msgtxt, tim)) - elif mtype == 'groupchat': - if subject: - self.dispatch('GC_SUBJECT', (frm, subject, msgtxt)) - else: - if not msg.getTag('body'): #no
- return - # Ignore message from room in which we are not - if not self.last_history_line.has_key(jid): - return - self.dispatch('GC_MSG', (frm, msgtxt, tim)) - if self.name not in no_log_for and jid in self.last_history_line \ - and not int(float(time.mktime(tim))) <= \ - self.last_history_line[jid]: - gajim.logger.write('gc_msg', frm, msgtxt, tim = tim) - elif mtype == 'chat': # it's type 'chat' - if not msg.getTag('body') and chatstate is None: #no - return - if msg.getTag('body') and self.name not in no_log_for and jid not in\ - no_log_for: - gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim, subject = subject) - self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject, - chatstate, msg_id, composing_jep)) - else: # it's single message - if self.name not in no_log_for and jid not in no_log_for: - gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim, subject = subject) - if invite is not None: - item = invite.getTag('invite') - jid_from = item.getAttr('from') - reason = item.getTagData('reason') - item = invite.getTag('password') - password = invite.getTagData('password') - self.dispatch('GC_INVITATION',(frm, jid_from, reason, password)) - else: - self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal', - subject, chatstate, msg_id, composing_jep)) - # END messageCB - - def _presenceCB(self, con, prs): - '''Called when we receive a presence''' - ptype = prs.getType() - if ptype == 'available': - ptype = None - gajim.log.debug('PresenceCB: %s' % ptype) - is_gc = False # is it a GC presence ? - sigTag = None - avatar_sha = None - xtags = prs.getTags('x') - for x in xtags: - if x.getNamespace().startswith(common.xmpp.NS_MUC): - is_gc = True - if x.getNamespace() == common.xmpp.NS_SIGNED: - sigTag = x - if x.getNamespace() == common.xmpp.NS_VCARD_UPDATE: - avatar_sha = x.getTagData('photo') - - who = self.get_full_jid(prs) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for') - status = prs.getStatus() - show = prs.getShow() - if not show in STATUS_LIST: - show = '' # We ignore unknown show - if not ptype and not show: - show = 'online' - elif ptype == 'unavailable': - show = 'offline' - - prio = prs.getPriority() - try: - prio = int(prio) - except: - prio = 0 - keyID = '' - if sigTag and USE_GPG: - #verify - sigmsg = sigTag.getData() - keyID = self.gpg.verify(status, sigmsg) - - if is_gc: - if ptype == 'error': - errmsg = prs.getError() - errcode = prs.getErrorCode() - if errcode == '502': # Internal Timeout: - self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, - prio, keyID)) - elif errcode == '401': # password required to join - self.dispatch('ERROR', (_('Unable to join room'), - _('A password is required to join this room.'))) - elif errcode == '403': # we are banned - self.dispatch('ERROR', (_('Unable to join room'), - _('You are banned from this room.'))) - elif errcode == '404': # room does not exist - self.dispatch('ERROR', (_('Unable to join room'), - _('Such room does not exist.'))) - elif errcode == '405': - self.dispatch('ERROR', (_('Unable to join room'), - _('Room creation is restricted.'))) - elif errcode == '406': - self.dispatch('ERROR', (_('Unable to join room'), - _('Your registered nickname must be used.'))) - elif errcode == '407': - self.dispatch('ERROR', (_('Unable to join room'), - _('You are not in the members list.'))) - elif errcode == '409': # nick conflict - # the jid_from in this case is FAKE JID: room_jid/nick - # resource holds the bad nick so propose a new one - proposed_nickname = resource + \ - gajim.config.get('gc_proposed_nick_char') - room_jid = gajim.get_room_from_fjid(who) - self.dispatch('ASK_NEW_NICK', (room_jid, _('Unable to join room'), - _('Your desired nickname is in use or registered by another occupant.\nPlease specify another nickname below:'), proposed_nickname)) - else: # print in the window the error - self.dispatch('ERROR_ANSWER', ('', jid_stripped, - errmsg, errcode)) - if not ptype or ptype == 'unavailable': - if gajim.config.get('log_contact_status_changes') and self.name\ - not in no_log_for and jid_stripped not in no_log_for: - gajim.logger.write('gcstatus', who, status, show) - self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource, - prs.getRole(), prs.getAffiliation(), prs.getJid(), - prs.getReason(), prs.getActor(), prs.getStatusCode(), - prs.getNewNick())) - return - - if ptype == 'subscribe': - gajim.log.debug('subscribe request from %s' % who) - if gajim.config.get('alwaysauth') or who.find("@") <= 0 or \ - jid_stripped in self.jids_for_auto_auth: - if self.connection: - p = common.xmpp.Presence(who, 'subscribed') - p = self.add_sha(p) - self.connection.send(p) - if who.find("@") <= 0: - self.dispatch('NOTIFY', - (jid_stripped, 'offline', 'offline', resource, prio, keyID)) - else: - if not status: - status = _('I would like to add you to my roster.') - self.dispatch('SUBSCRIBE', (who, status)) - elif ptype == 'subscribed': - self.dispatch('SUBSCRIBED', (jid_stripped, resource)) - # BE CAREFUL: no con.updateRosterItem() in a callback - gajim.log.debug(_('we are now subscribed to %s') % who) - elif ptype == 'unsubscribe': - gajim.log.debug(_('unsubscribe request from %s') % who) - elif ptype == 'unsubscribed': - gajim.log.debug(_('we are now unsubscribed from %s') % who) - self.dispatch('UNSUBSCRIBED', jid_stripped) - elif ptype == 'error': - errmsg = prs.getError() - errcode = prs.getErrorCode() - if errcode == '502': # Internal Timeout: - self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, - prio, keyID)) - else: # print in the window the error - self.dispatch('ERROR_ANSWER', ('', jid_stripped, - errmsg, errcode)) - - if avatar_sha and ptype != 'error': - if self.vcard_shas.has_key(jid_stripped): - if avatar_sha != self.vcard_shas[jid_stripped]: - # avatar has been updated - self.request_vcard(jid_stripped) - else: - self.vcard_shas[jid_stripped] = avatar_sha - if not ptype or ptype == 'unavailable': - if gajim.config.get('log_contact_status_changes') and self.name\ - not in no_log_for and jid_stripped not in no_log_for: - gajim.logger.write('status', jid_stripped, status, show) - self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio, - keyID)) - # END presenceCB - - def _disconnectedCB(self): - '''Called when we are disconnected''' - gajim.log.debug('disconnectedCB') - if not self.connection: - return - self.connected = 0 - self.dispatch('STATUS', 'offline') - self.connection = None - if not self.on_purpose: - self.dispatch('ERROR', - (_('Connection with account "%s" has been lost') % self.name, - _('To continue sending and receiving messages, you will need to reconnect.'))) - self.on_purpose = False - - # END disconenctedCB def _reconnect(self): # Do not try to reco while we are already trying @@ -708,970 +151,7 @@ class Connection: _('To continue sending and receiving messages, you will need to reconnect.'))) self.on_purpose = False # END disconenctedReconnCB - - def _bytestreamErrorCB(self, con, iq_obj): - gajim.log.debug('_bytestreamErrorCB') - id = unicode(iq_obj.getAttr('id')) - query = iq_obj.getTag('query') - jid = self.get_jid(iq_obj) - id = id[3:] - if not self.files_props.has_key(id): - return - file_props = self.files_props[id] - file_props['error'] = -4 - self.dispatch('FILE_REQUEST_ERROR', (jid, file_props)) - raise common.xmpp.NodeProcessed - - def _bytestreamSetCB(self, con, iq_obj): - gajim.log.debug('_bytestreamSetCB') - target = unicode(iq_obj.getAttr('to')) - id = unicode(iq_obj.getAttr('id')) - query = iq_obj.getTag('query') - sid = unicode(query.getAttr('sid')) - file_props = gajim.socks5queue.get_file_props( - self.name, sid) - streamhosts=[] - for item in query.getChildren(): - if item.getName() == 'streamhost': - host_dict={ - 'state': 0, - 'target': target, - 'id': id, - 'sid': sid, - 'initiator': self.get_full_jid(iq_obj) - } - for attr in item.getAttrs(): - host_dict[attr] = item.getAttr(attr) - streamhosts.append(host_dict) - if file_props is None: - if self.files_props.has_key(sid): - file_props = self.files_props[sid] - file_props['fast'] = streamhosts - if file_props['type'] == 's': # FIXME: remove fast xmlns - # only psi do this - - if file_props.has_key('streamhosts'): - file_props['streamhosts'].extend(streamhosts) - else: - file_props['streamhosts'] = streamhosts - if not gajim.socks5queue.get_file_props(self.name, sid): - gajim.socks5queue.add_file_props(self.name, file_props) - gajim.socks5queue.connect_to_hosts(self.name, sid, - self.send_success_connect_reply, None) - raise common.xmpp.NodeProcessed - - file_props['streamhosts'] = streamhosts - if file_props['type'] == 'r': - gajim.socks5queue.connect_to_hosts(self.name, sid, - self.send_success_connect_reply, self._connect_error) - raise common.xmpp.NodeProcessed - - def send_success_connect_reply(self, streamhost): - ''' send reply to the initiator of FT that we - made a connection - ''' - if streamhost is None: - return None - iq = common.xmpp.Iq(to = streamhost['initiator'], typ = 'result', - frm = streamhost['target']) - iq.setAttr('id', streamhost['id']) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - stream_tag = query.setTag('streamhost-used') - stream_tag.setAttr('jid', streamhost['jid']) - self.connection.send(iq) - - def _connect_error(self, to, _id, sid, code = 404): - msg_dict = { - 404: 'Could not connect to given hosts', - 405: 'Cancel', - 406: 'Not acceptable', - } - msg = msg_dict[code] - iq = None - iq = common.xmpp.Protocol(name = 'iq', to = to, - typ = 'error') - iq.setAttr('id', _id) - err = iq.setTag('error') - err.setAttr('code', unicode(code)) - err.setData(msg) - self.connection.send(iq) - if code == 404: - file_props = gajim.socks5queue.get_file_props(self.name, sid) - if file_props is not None: - self.disconnect_transfer(file_props) - file_props['error'] = -3 - self.dispatch('FILE_REQUEST_ERROR', (to, file_props)) - - def _bytestreamResultCB(self, con, iq_obj): - gajim.log.debug('_bytestreamResultCB') - frm = self.get_full_jid(iq_obj) - real_id = unicode(iq_obj.getAttr('id')) - query = iq_obj.getTag('query') - streamhost = None - try: - streamhost = query.getTag('streamhost') - except: - pass - if streamhost is not None: # this is a result for proxy request - jid = None - try: - jid = streamhost.getAttr('jid') - except: - raise common.xmpp.NodeProcessed - proxyhosts = [] - for item in query.getChildren(): - if item.getName() == 'streamhost': - host = item.getAttr('host') - port = item.getAttr('port') - jid = item.getAttr('jid') - conf = gajim.config - conf.add_per('ft_proxies65_cache', jid) - conf.set_per('ft_proxies65_cache', jid, - 'host', unicode(host)) - conf.set_per('ft_proxies65_cache', jid, - 'port', int(port)) - conf.set_per('ft_proxies65_cache', jid, - 'jid', unicode(jid)) - raise common.xmpp.NodeProcessed - try: - streamhost = query.getTag('streamhost-used') - except: # this bytestream result is not what we need - pass - id = real_id[3:] - if self.files_props.has_key(id): - file_props = self.files_props[id] - else: - raise common.xmpp.NodeProcessed - if streamhost is None: - # proxy approves the activate query - if real_id[:3] == 'au_': - id = real_id[3:] - if not file_props.has_key('streamhost-used') or \ - file_props['streamhost-used'] is False: - raise common.xmpp.NodeProcessed - if not file_props.has_key('proxyhosts'): - raise common.xmpp.NodeProcessed - for host in file_props['proxyhosts']: - if host['initiator'] == frm and \ - unicode(query.getAttr('sid')) == file_props['sid']: - gajim.socks5queue.activate_proxy(host['idx']) - break - raise common.xmpp.NodeProcessed - jid = streamhost.getAttr('jid') - if file_props.has_key('streamhost-used') and \ - file_props['streamhost-used'] is True: - raise common.xmpp.NodeProcessed - - if real_id[:3] == 'au_': - gajim.socks5queue.send_file(file_props, self.name) - raise common.xmpp.NodeProcessed - - proxy = None - if file_props.has_key('proxyhosts'): - for proxyhost in file_props['proxyhosts']: - if proxyhost['jid'] == jid: - proxy = proxyhost - - if proxy != None: - file_props['streamhost-used'] = True - if not file_props.has_key('streamhosts'): - file_props['streamhosts'] = [] - file_props['streamhosts'].append(proxy) - file_props['is_a_proxy'] = True - receiver = socks5.Socks5Receiver(gajim.idlequeue, proxy, file_props['sid'], file_props) - gajim.socks5queue.add_receiver(self.name, receiver) - proxy['idx'] = receiver.queue_idx - gajim.socks5queue.on_success = self.proxy_auth_ok - raise common.xmpp.NodeProcessed - - else: - gajim.socks5queue.send_file(file_props, self.name) - if file_props.has_key('fast'): - fasts = file_props['fast'] - if len(fasts) > 0: - self._connect_error(frm, fasts[0]['id'], file_props['sid'], - code = 406) - - raise common.xmpp.NodeProcessed - - def remove_all_transfers(self): - ''' stops and removes all active connections from the socks5 pool ''' - for file_props in self.files_props.values(): - self.remove_transfer(file_props, remove_from_list = False) - del(self.files_props) - self.files_props = {} - - def remove_transfer(self, file_props, remove_from_list = True): - if file_props is None: - return - self.disconnect_transfer(file_props) - sid = file_props['sid'] - gajim.socks5queue.remove_file_props(self.name, sid) - - if remove_from_list: - if self.files_props.has_key('sid'): - del(self.files_props['sid']) - - def disconnect_transfer(self, file_props): - if file_props is None: - return - if file_props.has_key('hash'): - gajim.socks5queue.remove_sender(file_props['hash']) - - if file_props.has_key('streamhosts'): - for host in file_props['streamhosts']: - if host.has_key('idx') and host['idx'] > 0: - gajim.socks5queue.remove_receiver(host['idx']) - gajim.socks5queue.remove_sender(host['idx']) - - def proxy_auth_ok(self, proxy): - '''cb, called after authentication to proxy server ''' - file_props = self.files_props[proxy['sid']] - iq = common.xmpp.Protocol(name = 'iq', to = proxy['initiator'], - typ = 'set') - auth_id = "au_" + proxy['sid'] - iq.setID(auth_id) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - query.setAttr('sid', proxy['sid']) - activate = query.setTag('activate') - activate.setData(file_props['proxy_receiver']) - iq.setID(auth_id) - self.connection.send(iq) - - def _discoGetCB(self, con, iq_obj): - ''' get disco info ''' - frm = self.get_full_jid(iq_obj) - to = unicode(iq_obj.getAttr('to')) - id = unicode(iq_obj.getAttr('id')) - iq = common.xmpp.Iq(to = frm, typ = 'result', queryNS =\ - common.xmpp.NS_DISCO, frm = to) - iq.setAttr('id', id) - query = iq.setTag('query') - # bytestream transfers - feature = common.xmpp.Node('feature') - feature.setAttr('var', common.xmpp.NS_BYTESTREAM) - query.addChild(node=feature) - # si methods - feature = common.xmpp.Node('feature') - feature.setAttr('var', common.xmpp.NS_SI) - query.addChild(node=feature) - # filetransfers transfers - feature = common.xmpp.Node('feature') - feature.setAttr('var', common.xmpp.NS_FILE) - query.addChild(node=feature) - - self.connection.send(iq) - raise common.xmpp.NodeProcessed - - def _siResultCB(self, con, iq_obj): - gajim.log.debug('_siResultCB') - id = iq_obj.getAttr('id') - if not self.files_props.has_key(id): - # no such jid - return - file_props = self.files_props[id] - if file_props is None: - # file properties for jid is none - return - file_props['receiver'] = self.get_full_jid(iq_obj) - si = iq_obj.getTag('si') - file_tag = si.getTag('file') - range_tag = None - if file_tag: - range_tag = file_tag.getTag('range') - if range_tag: - offset = range_tag.getAttr('offset') - if offset: - file_props['offset'] = int(offset) - length = range_tag.getAttr('length') - if length: - file_props['length'] = int(length) - feature = si.setTag('feature') - if feature.getNamespace() != common.xmpp.NS_FEATURE: - return - form_tag = feature.getTag('x') - form = common.xmpp.DataForm(node=form_tag) - field = form.getField('stream-method') - if field.getValue() != common.xmpp.NS_BYTESTREAM: - return - self.send_socks5_info(file_props, fast = True) - raise common.xmpp.NodeProcessed - - def _get_sha(self, sid, initiator, target): - return sha.new("%s%s%s" % (sid, initiator, target)).hexdigest() - - def result_socks5_sid(self, sid, hash_id): - ''' store the result of sha message from auth ''' - if not self.files_props.has_key(sid): - return - file_props = self.files_props[sid] - file_props['hash'] = hash_id - return - - def get_cached_proxies(self, proxy): - ''' get cached entries for proxy and request the cache again ''' - host = gajim.config.get_per('ft_proxies65_cache', proxy, 'host') - port = gajim.config.get_per('ft_proxies65_cache', proxy, 'port') - jid = gajim.config.get_per('ft_proxies65_cache', proxy, 'jid') - - iq = common.xmpp.Protocol(name = 'iq', to = proxy, typ = 'get') - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - # FIXME bad logic - this should be somewhere else! - # this line should be put somewhere else - # self.connection.send(iq) - # ensure that we don;t return empty vars - if None not in (host, port, jid) or '' not in (host, port, jid): - return (host, port, jid) - return (None, None, None) - - def send_socks5_info(self, file_props, fast = True, receiver = None, - sender = None): - ''' send iq for the present streamhosts and proxies ''' - if type(self.peerhost) != tuple: - return - port = gajim.config.get('file_transfers_port') - ft_override_host_to_send = gajim.config.get('ft_override_host_to_send') - cfg_proxies = gajim.config.get_per('accounts', self.name, - 'file_transfer_proxies') - if receiver is None: - receiver = file_props['receiver'] - if sender is None: - sender = file_props['sender'] - proxyhosts = [] - if fast and cfg_proxies: - proxies = map(lambda e:e.strip(), cfg_proxies.split(',')) - for proxy in proxies: - (host, _port, jid) = self.get_cached_proxies(proxy) - if host is None: - continue - host_dict={ - 'state': 0, - 'target': unicode(receiver), - 'id': file_props['sid'], - 'sid': file_props['sid'], - 'initiator': proxy, - 'host': host, - 'port': unicode(_port), - 'jid': jid - } - proxyhosts.append(host_dict) - sha_str = self._get_sha(file_props['sid'], sender, - receiver) - file_props['sha_str'] = sha_str - if not ft_override_host_to_send: - ft_override_host_to_send = self.peerhost[0] - ft_override_host_to_send = socket.gethostbyname(ft_override_host_to_send) - listener = gajim.socks5queue.start_listener(self.peerhost[0], port, - sha_str, self.result_socks5_sid, file_props['sid']) - if listener == None: - file_props['error'] = -5 - self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props)) - self._connect_error(unicode(receiver), file_props['sid'], - file_props['sid'], code = 406) - return - - iq = common.xmpp.Protocol(name = 'iq', to = unicode(receiver), - typ = 'set') - file_props['request-id'] = 'id_' + file_props['sid'] - iq.setID(file_props['request-id']) - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_BYTESTREAM) - query.setAttr('mode', 'tcp') - query.setAttr('sid', file_props['sid']) - streamhost = query.setTag('streamhost') - streamhost.setAttr('port', unicode(port)) - streamhost.setAttr('host', ft_override_host_to_send) - streamhost.setAttr('jid', sender) - if fast and proxyhosts != []: - file_props['proxy_receiver'] = unicode(receiver) - file_props['proxy_sender'] = unicode(sender) - file_props['proxyhosts'] = proxyhosts - for proxyhost in proxyhosts: - streamhost = common.xmpp.Node(tag = 'streamhost') - query.addChild(node=streamhost) - streamhost.setAttr('port', proxyhost['port']) - streamhost.setAttr('host', proxyhost['host']) - streamhost.setAttr('jid', proxyhost['jid']) - - # don't add the proxy child tag for streamhosts, which are proxies - # proxy = streamhost.setTag('proxy') - # proxy.setNamespace(common.xmpp.NS_STREAM) - self.connection.send(iq) - - def _siSetCB(self, con, iq_obj): - gajim.log.debug('_siSetCB') - jid = self.get_jid(iq_obj) - si = iq_obj.getTag('si') - profile = si.getAttr('profile') - mime_type = si.getAttr('mime-type') - if profile != common.xmpp.NS_FILE: - return - file_tag = si.getTag('file') - file_props = {'type': 'r'} - for attribute in file_tag.getAttrs(): - if attribute in ('name', 'size', 'hash', 'date'): - val = file_tag.getAttr(attribute) - if val is None: - continue - file_props[attribute] = val - file_desc_tag = file_tag.getTag('desc') - if file_desc_tag is not None: - file_props['desc'] = file_desc_tag.getData() - - if mime_type is not None: - file_props['mime-type'] = mime_type - our_jid = gajim.get_jid_from_account(self.name) - resource = self.server_resource - file_props['receiver'] = our_jid + '/' + resource - file_props['sender'] = self.get_full_jid(iq_obj) - file_props['request-id'] = unicode(iq_obj.getAttr('id')) - file_props['sid'] = unicode(si.getAttr('id')) - gajim.socks5queue.add_file_props(self.name, file_props) - self.dispatch('FILE_REQUEST', (jid, file_props)) - raise common.xmpp.NodeProcessed - - def _siErrorCB(self, con, iq_obj): - gajim.log.debug('_siErrorCB') - si = iq_obj.getTag('si') - profile = si.getAttr('profile') - if profile != common.xmpp.NS_FILE: - return - id = iq_obj.getAttr('id') - if not self.files_props.has_key(id): - # no such jid - return - file_props = self.files_props[id] - if file_props is None: - # file properties for jid is none - return - jid = self.get_jid(iq_obj) - file_props['error'] = -3 - self.dispatch('FILE_REQUEST_ERROR', (jid, file_props)) - raise common.xmpp.NodeProcessed - - def send_file_rejection(self, file_props): - ''' informs sender that we refuse to download the file ''' - iq = common.xmpp.Protocol(name = 'iq', - to = unicode(file_props['sender']), typ = 'error') - iq.setAttr('id', file_props['request-id']) - err = common.xmpp.ErrorNode(code = '406', typ = 'auth', name = - 'not-acceptable') - iq.addChild(node=err) - self.connection.send(iq) - - def send_file_approval(self, file_props): - ''' comfirm that we want to download the file ''' - iq = common.xmpp.Protocol(name = 'iq', - to = unicode(file_props['sender']), typ = 'result') - iq.setAttr('id', file_props['request-id']) - si = iq.setTag('si') - si.setNamespace(common.xmpp.NS_SI) - file_tag = si.setTag('file') - file_tag.setNamespace(common.xmpp.NS_FILE) - if file_props.has_key('offset') and file_props['offset']: - range_tag = file_tag.setTag('range') - range_tag.setAttr('offset', file_props['offset']) - feature = si.setTag('feature') - feature.setNamespace(common.xmpp.NS_FEATURE) - _feature = common.xmpp.DataForm(typ='submit') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.delAttr('type') - field.setValue(common.xmpp.NS_BYTESTREAM) - self.connection.send(iq) - - def send_file_request(self, file_props): - our_jid = gajim.get_jid_from_account(self.name) - resource = self.server_resource - frm = our_jid + '/' + resource - file_props['sender'] = frm - fjid = file_props['receiver'].jid + '/' + file_props['receiver'].resource - iq = common.xmpp.Protocol(name = 'iq', to = fjid, - typ = 'set') - iq.setID(file_props['sid']) - self.files_props[file_props['sid']] = file_props - si = iq.setTag('si') - si.setNamespace(common.xmpp.NS_SI) - si.setAttr('profile', common.xmpp.NS_FILE) - si.setAttr('id', file_props['sid']) - file_tag = si.setTag('file') - file_tag.setNamespace(common.xmpp.NS_FILE) - file_tag.setAttr('name', file_props['name']) - file_tag.setAttr('size', file_props['size']) - desc = file_tag.setTag('desc') - if file_props.has_key('desc'): - desc.setData(file_props['desc']) - file_tag.setTag('range') - feature = si.setTag('feature') - feature.setNamespace(common.xmpp.NS_FEATURE) - _feature = common.xmpp.DataForm(typ='form') - feature.addChild(node=_feature) - field = _feature.setField('stream-method') - field.setAttr('type', 'list-single') - field.addOption(common.xmpp.NS_BYTESTREAM) - self.connection.send(iq) - - def _rosterSetCB(self, con, iq_obj): - gajim.log.debug('rosterSetCB') - for item in iq_obj.getTag('query').getChildren(): - jid = helpers.parse_jid(item.getAttr('jid')) - name = item.getAttr('name') - sub = item.getAttr('subscription') - ask = item.getAttr('ask') - groups = [] - for group in item.getTags('group'): - groups.append(group.getData()) - self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups)) - raise common.xmpp.NodeProcessed - - def _DiscoverItemsErrorCB(self, con, iq_obj): - gajim.log.debug('DiscoverItemsErrorCB') - jid = self.get_full_jid(iq_obj) - self.dispatch('AGENT_ERROR_ITEMS', (jid)) - - def _DiscoverItemsCB(self, con, iq_obj): - gajim.log.debug('DiscoverItemsCB') - q = iq_obj.getTag('query') - node = q.getAttr('node') - if not node: - node = '' - qp = iq_obj.getQueryPayload() - items = [] - if not qp: - qp = [] - for i in qp: - # CDATA payload is not processed, only nodes - if not isinstance(i, common.xmpp.simplexml.Node): - continue - attr = {} - for key in i.getAttrs(): - attr[key] = i.getAttrs()[key] - items.append(attr) - jid = self.get_full_jid(iq_obj) - self.dispatch('AGENT_INFO_ITEMS', (jid, node, items)) - - def _DiscoverInfoGetCB(self, con, iq_obj): - gajim.log.debug('DiscoverInfoGetCB') - iq = iq_obj.buildReply('result') - q = iq.getTag('query') - q.addChild('identity', attrs = {'type': 'pc', - 'category': 'client', - 'name': 'Gajim'}) - q.addChild('feature', attrs = {'var': common.xmpp.NS_BYTESTREAM}) - q.addChild('feature', attrs = {'var': common.xmpp.NS_SI}) - q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE}) - q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC}) - self.connection.send(iq) - raise common.xmpp.NodeProcessed - - def _DiscoverInfoErrorCB(self, con, iq_obj): - gajim.log.debug('DiscoverInfoErrorCB') - jid = self.get_full_jid(iq_obj) - self.dispatch('AGENT_ERROR_INFO', (jid)) - - def _DiscoverInfoCB(self, con, iq_obj): - gajim.log.debug('DiscoverInfoCB') - # According to JEP-0030: - # For identity: category, type is mandatory, name is optional. - # For feature: var is mandatory - identities, features, data = [], [], [] - q = iq_obj.getTag('query') - node = q.getAttr('node') - if not node: - node = '' - qc = iq_obj.getQueryChildren() - if not qc: - qc = [] - for i in qc: - if i.getName() == 'identity': - attr = {} - for key in i.getAttrs().keys(): - attr[key] = i.getAttr(key) - identities.append(attr) - elif i.getName() == 'feature': - features.append(i.getAttr('var')) - elif i.getName() == 'x' and i.getAttr('xmlns') == common.xmpp.NS_DATA: - data.append(common.xmpp.DataForm(node=i)) - jid = self.get_full_jid(iq_obj) - if identities: #if not: an error occured - self.dispatch('AGENT_INFO_INFO', (jid, node, identities, - features, data)) - - def _VersionCB(self, con, iq_obj): - gajim.log.debug('VersionCB') - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.getTag('query') - qp.setTagData('name', 'Gajim') - qp.setTagData('version', gajim.version) - send_os = gajim.config.get('send_os_info') - if send_os: - qp.setTagData('os', get_os_info()) - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _LastCB(self, con, iq_obj): - gajim.log.debug('IdleCB') - iq_obj = iq_obj.buildReply('result') - qp = iq_obj.getTag('query') - if not HAS_IDLE: - qp.attrs['seconds'] = '0'; - else: - qp.attrs['seconds'] = idle.getIdleSec() - - self.connection.send(iq_obj) - raise common.xmpp.NodeProcessed - - def _LastResultCB(self, con, iq_obj): - gajim.log.debug('LastResultCB') - qp = iq_obj.getTag('query') - seconds = qp.getAttr('seconds') - status = qp.getData() - try: - seconds = int(seconds) - except: - return - who = self.get_full_jid(iq_obj) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status)) - - def _VersionResultCB(self, con, iq_obj): - gajim.log.debug('VersionResultCB') - client_info = '' - os_info = '' - qp = iq_obj.getTag('query') - if qp.getTag('name'): - client_info += qp.getTag('name').getData() - if qp.getTag('version'): - client_info += ' ' + qp.getTag('version').getData() - if qp.getTag('os'): - os_info += qp.getTag('os').getData() - who = self.get_full_jid(iq_obj) - jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) - self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info)) - - def parse_data_form(self, node): - dic = {} - tag = node.getTag('title') - if tag: - dic['title'] = tag.getData() - tag = node.getTag('instructions') - if tag: - dic['instructions'] = tag.getData() - i = 0 - for child in node.getChildren(): - if child.getName() != 'field': - continue - var = child.getAttr('var') - ctype = child.getAttr('type') - label = child.getAttr('label') - if not var and ctype != 'fixed': # We must have var if type != fixed - continue - dic[i] = {} - if var: - dic[i]['var'] = var - if ctype: - dic[i]['type'] = ctype - if label: - dic[i]['label'] = label - tags = child.getTags('value') - if len(tags): - dic[i]['values'] = [] - for tag in tags: - data = tag.getData() - if ctype == 'boolean': - if data in ('yes', 'true', 'assent', '1'): - data = True - else: - data = False - dic[i]['values'].append(data) - tag = child.getTag('desc') - if tag: - dic[i]['desc'] = tag.getData() - option_tags = child.getTags('option') - if len(option_tags): - dic[i]['options'] = {} - j = 0 - for option_tag in option_tags: - dic[i]['options'][j] = {} - label = option_tag.getAttr('label') - tags = option_tag.getTags('value') - dic[i]['options'][j]['values'] = [] - for tag in tags: - dic[i]['options'][j]['values'].append(tag.getData()) - if not label: - label = dic[i]['options'][j]['values'][0] - dic[i]['options'][j]['label'] = label - j += 1 - if not dic[i].has_key('values'): - dic[i]['values'] = [dic[i]['options'][0]['values'][0]] - i += 1 - return dic - - def _MucOwnerCB(self, con, iq_obj): - gajim.log.debug('MucOwnerCB') - qp = iq_obj.getQueryPayload() - node = None - for q in qp: - if q.getNamespace() == common.xmpp.NS_DATA: - node = q - if not node: - return - dic = self.parse_data_form(node) - self.dispatch('GC_CONFIG', (self.get_full_jid(iq_obj), dic)) - - def _MucAdminCB(self, con, iq_obj): - gajim.log.debug('MucAdminCB') - items = iq_obj.getTag('query', namespace = common.xmpp.NS_MUC_ADMIN).getTags('item') - list = {} - affiliation = '' - for item in items: - if item.has_attr('jid') and item.has_attr('affiliation'): - jid = item.getAttr('jid') - affiliation = item.getAttr('affiliation') - list[jid] = {'affiliation': affiliation} - if item.has_attr('nick'): - list[jid]['nick'] = item.getAttr('nick') - if item.has_attr('role'): - list[jid]['role'] = item.getAttr('role') - reason = item.getTagData('reason') - if reason: - list[jid]['reason'] = reason - - self.dispatch('GC_AFFILIATION', (self.get_full_jid(iq_obj), affiliation, list)) - - def _MucErrorCB(self, con, iq_obj): - gajim.log.debug('MucErrorCB') - jid = self.get_full_jid(iq_obj) - errmsg = iq_obj.getError() - errcode = iq_obj.getErrorCode() - self.dispatch('MSGERROR', (jid, errcode, errmsg)) - - def _getRosterCB(self, con, iq_obj): - if not self.connection: - return - self.connection.getRoster(self._on_roster_set) - - def _on_roster_set(self, roster): - raw_roster = roster.getRaw() - roster = {} - for jid in raw_roster: - try: - j = helpers.parse_jid(jid) - except: - print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid - else: - roster[j] = raw_roster[jid] - - # Remove or jid - our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) - if roster.has_key(our_jid): - del roster[our_jid] - - self.dispatch('ROSTER', roster) - - # continue connection - if self.connected > 1 and self.continue_connect_info: - show = self.continue_connect_info[0] - msg = self.continue_connect_info[1] - signed = self.continue_connect_info[2] - self.connected = STATUS_LIST.index(show) - sshow = helpers.get_xmpp_show(show) - # send our presence - if show == 'invisible': - self.send_invisible_presence(msg, signed, True) - return - prio = unicode(gajim.config.get_per('accounts', self.name, - 'priority')) - vcard = self.get_cached_vcard(jid) - if vcard and vcard.has_key('PHOTO') and vcard['PHOTO'].has_key('SHA'): - self.vcard_sha = vcard['PHOTO']['SHA'] - p = common.xmpp.Presence(typ = None, priority = prio, show = sshow) - p = self.add_sha(p) - if msg: - p.setStatus(msg) - if signed: - p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) - - if self.connection: - self.connection.send(p) - self.dispatch('STATUS', show) - # ask our VCard - self.request_vcard(None) - - # Get bookmarks from private namespace - self.get_bookmarks() - - # If it's a gmail account, - # inform the server that we want e-mail notifications - if gajim.get_server_from_jid(our_jid) == 'gmail.com': - gajim.log.debug(('%s is a gmail account. Setting option ' - 'to get e-mail notifications on the server.') % (our_jid)) - iq = common.xmpp.Iq(typ = 'set', to = our_jid) - iq.setAttr('id', 'MailNotify') - query = iq.setTag('usersetting') - query.setNamespace(common.xmpp.NS_GTALKSETTING) - query = query.setTag('mailnotifications') - query.setAttr('value', 'true') - self.connection.send(iq) - # Ask how many messages there are now - iq = common.xmpp.Iq(typ = 'get') - iq.setAttr('id', '13') - query = iq.setTag('query') - query.setNamespace(common.xmpp.NS_GMAILNOTIFY) - self.connection.send(iq) - - #Inform GUI we just signed in - self.dispatch('SIGNED_IN', ()) - self.continue_connect_info = None - - def _PrivateCB(self, con, iq_obj): - ''' - Private Data (JEP 048 and 049) - ''' - gajim.log.debug('PrivateCB') - query = iq_obj.getTag('query') - storage = query.getTag('storage') - if storage: - ns = storage.getNamespace() - if ns == 'storage:bookmarks': - # Bookmarked URLs and Conferences - # http://www.jabber.org/jeps/jep-0048.html - confs = storage.getTags('conference') - for conf in confs: - autojoin_val = conf.getAttr('autojoin') - if autojoin_val is None: # not there (it's optional) - autojoin_val = False - bm = { 'name': conf.getAttr('name'), - 'jid': conf.getAttr('jid'), - 'autojoin': autojoin_val, - 'password': conf.getTagData('password'), - 'nick': conf.getTagData('nick') } - - self.bookmarks.append(bm) - self.dispatch('BOOKMARKS', self.bookmarks) - - gajim_tag = query.getTag('gajim') - if gajim_tag: - ns = gajim_tag.getNamespace() - if ns == 'gajim:metacontacts': - # Meta contacts list - children_list = {} - for child in gajim_tag.getChildren(): - parent_jid = child.getAttr('name') - if not parent_jid: - continue - children_list[parent_jid] = [] - for cchild in child.getChildren(): - children_list[parent_jid].append(cchild.getAttr('name')) - self.dispatch('META_CONTACTS', children_list) - # We can now continue connection by requesting the roster - self.connection.initRoster() - - elif ns == 'gajim:prefs': - # Preferences data - # http://www.jabber.org/jeps/jep-0049.html - #TODO: implement this - pass - - def _PrivateErrorCB(self, con, iq_obj): - gajim.log.debug('PrivateErrorCB') - query = iq_obj.getTag('query') - gajim_tag = query.getTag('gajim') - if gajim_tag: - ns = gajim_tag.getNamespace() - if ns == 'gajim:metacontacts': - # Private XML Storage (JEP49) is not supported by server - # Continue connecting - self.connection.initRoster() - - def build_http_auth_answer(self, iq_obj, answer): - if answer == 'yes': - iq = iq_obj.buildReply('result') - elif answer == 'no': - iq = iq_obj.buildReply('error') - iq.setError('not-authorized', 401) - self.connection.send(iq) - - def _HttpAuthCB(self, con, iq_obj): - gajim.log.debug('HttpAuthCB') - opt = gajim.config.get_per('accounts', self.name, 'http_auth') - if opt in ('yes', 'no'): - self.build_http_auth_answer(iq_obj, opt) - else: - id = iq_obj.getTagAttr('confirm', 'id') - method = iq_obj.getTagAttr('confirm', 'method') - url = iq_obj.getTagAttr('confirm', 'url') - self.dispatch('HTTP_AUTH', (method, url, id, iq_obj)); - raise common.xmpp.NodeProcessed - - def _ErrorCB(self, con, iq_obj): - errmsg = iq_obj.getError() - errcode = iq_obj.getErrorCode() - jid_from = self.get_full_jid(iq_obj) - id = unicode(iq_obj.getID()) - self.dispatch('ERROR_ANSWER', (id, jid_from, errmsg, errcode)) - - def _StanzaArrivedCB(self, con, obj): - self.last_io = time.time() - - def _IqCB(self, con, iq_obj): - id = iq_obj.getID() - - # Check if we were waiting a timeout for this id - found_tim = None - for tim in self.awaiting_timeouts: - if id == self.awaiting_timeouts[tim][0]: - found_tim = tim - break - if found_tim: - del self.awaiting_timeouts[found_tim] - - if id not in self.awaiting_answers: - return - if self.awaiting_answers[id][0] == VCARD_PUBLISHED: - if iq_obj.getType() == 'result': - self.dispatch('VCARD_PUBLISHED', ()) - vcard_iq = self.awaiting_answers[id][1] - # Save vcard to HD - if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag('SHA'): - new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA') - else: - new_sha = '' - - # Save it to file - our_jid = gajim.get_jid_from_account(self.name) - self.save_vcard_to_hd(our_jid, vcard_iq) - - # Send new presence if sha changed and we are not invisible - if self.vcard_sha != new_sha and STATUS_LIST[self.connected] != \ - 'invisible': - self.vcard_sha = new_sha - sshow = helpers.get_xmpp_show(STATUS_LIST[self.connected]) - prio = unicode(gajim.config.get_per('accounts', self.name, - 'priority')) - p = common.xmpp.Presence(typ = None, priority = prio, - show = sshow, status = self.status) - p = self.add_sha(p) - self.connection.send(p) - elif iq_obj.getType() == 'error': - self.dispatch('VCARD_NOT_PUBLISHED', ()) - elif self.awaiting_answers[id][0] == VCARD_ARRIVED: - # If vcard is empty, we send to the interface an empty vcard so that - # it knows it arrived - if not iq_obj.getTag('vCard'): - jid = self.awaiting_answers[id][1] - our_jid = gajim.get_jid_from_account(self.name) - if jid and jid != our_jid: - # Write an empty file - self.save_vcard_to_hd(jid, '') - self.dispatch('VCARD', {'jid': jid}) - elif jid == our_jid: - self.dispatch('MYVCARD', {'jid': jid}) - del self.awaiting_answers[id] - + def _event_dispatcher(self, realm, event, data): if realm == common.xmpp.NS_REGISTER: if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED: @@ -1872,67 +352,7 @@ class Connection: self.peerhost = con.get_peerhost() # notify the gui about con_type self.dispatch('CON_TYPE', con_type) - - con.RegisterHandler('message', self._messageCB) - con.RegisterHandler('presence', self._presenceCB) - con.RegisterHandler('iq', self._vCardCB, 'result', - common.xmpp.NS_VCARD) - con.RegisterHandler('iq', self._rosterSetCB, 'set', - common.xmpp.NS_ROSTER) - con.RegisterHandler('iq', self._siSetCB, 'set', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._siErrorCB, 'error', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._siResultCB, 'result', - common.xmpp.NS_SI) - con.RegisterHandler('iq', self._discoGetCB, 'get', - common.xmpp.NS_DISCO) - con.RegisterHandler('iq', self._bytestreamSetCB, 'set', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._bytestreamResultCB, 'result', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._bytestreamErrorCB, 'error', - common.xmpp.NS_BYTESTREAM) - con.RegisterHandler('iq', self._DiscoverItemsCB, 'result', - common.xmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error', - common.xmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._DiscoverInfoCB, 'result', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._VersionCB, 'get', - common.xmpp.NS_VERSION) - con.RegisterHandler('iq', self._LastCB, 'get', - common.xmpp.NS_LAST) - con.RegisterHandler('iq', self._LastResultCB, 'result', - common.xmpp.NS_LAST) - con.RegisterHandler('iq', self._VersionResultCB, 'result', - common.xmpp.NS_VERSION) - con.RegisterHandler('iq', self._MucOwnerCB, 'result', - common.xmpp.NS_MUC_OWNER) - con.RegisterHandler('iq', self._MucAdminCB, 'result', - common.xmpp.NS_MUC_ADMIN) - con.RegisterHandler('iq', self._getRosterCB, 'result', - common.xmpp.NS_ROSTER) - con.RegisterHandler('iq', self._PrivateCB, 'result', - common.xmpp.NS_PRIVATE) - con.RegisterHandler('iq', self._PrivateErrorCB, 'error', - common.xmpp.NS_PRIVATE) - con.RegisterHandler('iq', self._HttpAuthCB, 'get', - common.xmpp.NS_HTTP_AUTH) - con.RegisterHandler('iq', self._gMailNewMailCB, 'set', - common.xmpp.NS_GMAILNOTIFY) - con.RegisterHandler('iq', self._gMailQueryCB, 'result', - common.xmpp.NS_GMAILNOTIFY) - con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get', - common.xmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._ErrorCB, 'error') - con.RegisterHandler('iq', self._IqCB) - con.RegisterHandler('iq', self._StanzaArrivedCB) - con.RegisterHandler('presence', self._StanzaArrivedCB) - con.RegisterHandler('message', self._StanzaArrivedCB) - + ConnectionHandlers._register_handlers(self, con, con_type) name = gajim.config.get_per('accounts', self.name, 'name') hostname = gajim.config.get_per('accounts', self.name, 'hostname') resource = gajim.config.get_per('accounts', self.name, 'resource') @@ -1957,7 +377,7 @@ class Connection: if hasattr(con, 'Resource'): self.server_resource = con.Resource if auth: - self.last_io = time.time() + self.last_io = gajim.idlequeue.current_time() self.connected = 2 if self.on_connect_auth: self.on_connect_auth(con) @@ -2079,9 +499,6 @@ class Connection: self.get_meta_contacts() def change_status(self, show, msg, sync = False, auto = False): - self.change_status2(show, msg, auto) - - def change_status2(self, show, msg, auto = False): if not show in STATUS_LIST: return -1 sshow = helpers.get_xmpp_show(show) @@ -2220,12 +637,7 @@ class Connection: gajim.logger.write(kind, jid, log_msg) self.dispatch('MSGSENT', (jid, msg, keyID)) - def send_stanza(self, stanza): - ''' send a stanza untouched ''' - if not self.connection: - return - self.connection.send(stanza) - + def ack_subscribed(self, jid): if not self.connection: return @@ -2308,40 +720,8 @@ class Connection: if self.connection: self.connection.getRoster().setItem(jid = jid, name = name, groups = groups) - - def _ReceivedRegInfo(self, con, resp, agent): - common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent) - self._IqCB(con, resp) - - def request_register_agent_info(self, agent): - if not self.connection: - return None - iq=common.xmpp.Iq('get', common.xmpp.NS_REGISTER, to=agent) - id = self.connection.getAnID() - iq.setID(id) - # Wait the answer during 30 secondes - self.awaiting_timeouts[time.time() + 30] = (id, - _('Registration information for transport %s has not arrived in time' % \ - agent)) - self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo, - {'agent': agent}) - - def register_agent(self, agent, info, is_form = False): - if not self.connection: - return - if is_form: - iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) - query = iq.getTag('query') - self.build_data_from_dict(query, info) - self.connection.send(iq) - else: - # fixed: blocking - common.xmpp.features_nb.register(self.connection, agent, info, None) - + def new_account(self, name, config, sync = False): - self.new_account2(name, config) - - def new_account2(self, name, config): # If a connection already exist we cannot create a new account if self.connection: return @@ -2382,93 +762,6 @@ class Connection: common.xmpp.NS_VERSION) self.connection.send(iq) - def get_cached_vcard(self, fjid, is_fake_jid = False): - '''return the vcard as a dict - return {} if vcard was too old - return None if we don't have cached vcard''' - jid, nick = gajim.get_room_and_nick_from_fjid(fjid) - nick = nick.replace('/', '_') - puny_jid = punycode_encode(jid) - if is_fake_jid: - puny_nick = punycode_encode(nick) - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) - else: - path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid) - if not os.path.isfile(path_to_file): - return None - # We have the vcard cached - f = open(path_to_file) - c = f.read() - f.close() - card = common.xmpp.Node(node = c) - vcard = self.node_to_dict(card) - if vcard.has_key('PHOTO'): - if not isinstance(vcard['PHOTO'], dict): - del vcard['PHOTO'] - elif vcard['PHOTO'].has_key('SHA'): - cached_sha = vcard['PHOTO']['SHA'] - if self.vcard_shas.has_key(jid) and self.vcard_shas[jid] != \ - cached_sha: - # user change his vcard so don't use the cached one - return {} - vcard['jid'] = jid - vcard['resource'] = gajim.get_resource_from_jid(fjid) - return vcard - - def request_vcard(self, jid = None, is_fake_jid = False): - '''request the VCARD. If is_fake_jid is True, it means we request a vcard - to a fake jid, like in private messages in groupchat''' - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'get') - if jid: - iq.setTo(jid) - iq.setTag(common.xmpp.NS_VCARD + ' vCard') - - id = self.connection.getAnID() - iq.setID(id) - self.awaiting_answers[id] = (VCARD_ARRIVED, jid) - if is_fake_jid: - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - if not room_jid in self.room_jids: - self.room_jids.append(room_jid) - self.connection.send(iq) - #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...}) - - def send_vcard(self, vcard): - if not self.connection: - return - iq = common.xmpp.Iq(typ = 'set') - iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard') - for i in vcard: - if i == 'jid': - continue - if isinstance(vcard[i], dict): - iq3 = iq2.addChild(i) - for j in vcard[i]: - iq3.addChild(j).setData(vcard[i][j]) - elif type(vcard[i]) == type([]): - for j in vcard[i]: - iq3 = iq2.addChild(i) - for k in j: - iq3.addChild(k).setData(j[k]) - else: - iq2.addChild(i).setData(vcard[i]) - - id = self.connection.getAnID() - iq.setID(id) - self.connection.send(iq) - - # Add the sha of the avatar - if vcard.has_key('PHOTO') and isinstance(vcard['PHOTO'], dict) and \ - vcard['PHOTO'].has_key('BINVAL'): - photo = vcard['PHOTO']['BINVAL'] - photo_decoded = base64.decodestring(photo) - avatar_sha = sha.sha(photo_decoded).hexdigest() - iq2.getTag('PHOTO').setTagData('SHA', avatar_sha) - - self.awaiting_answers[id] = (VCARD_PUBLISHED, iq2) - def get_settings(self): ''' Get Gajim settings as described in JEP 0049 ''' if not self.connection: @@ -2646,29 +939,6 @@ class Connection: item.setAttr('affiliation', affiliation) self.connection.send(iq) - def build_data_from_dict(self, query, config): - x = query.setTag(common.xmpp.NS_DATA + ' x', attrs = {'type': 'submit'}) - i = 0 - while config.has_key(i): - if not config[i].has_key('type'): - i += 1 - continue - if config[i]['type'] == 'fixed': - i += 1 - continue - tag = x.addChild('field') - if config[i].has_key('var'): - tag.setAttr('var', config[i]['var']) - if config[i].has_key('values'): - for val in config[i]['values']: - if val == False: - val = '0' - elif val == True: - val = '1' - # Force to create a new child - tag.addChild('value').addData(val) - i += 1 - def send_gc_config(self, room_jid, config): iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ common.xmpp.NS_MUC_OWNER) diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py new file mode 100644 index 000000000..173241f14 --- /dev/null +++ b/src/common/connection_handlers.py @@ -0,0 +1,1658 @@ +## +## Copyright (C) 2006 Gajim Team +## +## Contributors for this file: +## - Yann Le Boulanger