diff --git a/src/common/connection_handlers_zeroconf.py b/src/common/connection_handlers_zeroconf.py new file mode 100644 index 000000000..1a82dc682 --- /dev/null +++ b/src/common/connection_handlers_zeroconf.py @@ -0,0 +1,1781 @@ +## +## Copyright (C) 2006 Gajim Team +## +## Contributors for this file: +## - Yann Le Boulanger +## - Nikos Kouremenos +## - Dimitur Kirov +## - Travis Shirk +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program 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. +## + +import os +import time +import base64 +import sha +import socket +import sys + +from calendar import timegm + +import socks5 +import common.xmpp + +from common import GnuPG +from common import helpers +from common import gajim +from common import i18n +_ = i18n._ + +STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', + 'invisible'] +# kind of events we can wait for an answer +VCARD_PUBLISHED = 'vcard_published' +VCARD_ARRIVED = 'vcard_arrived' +AGENT_REMOVED = 'agent_removed' +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 + +class ConnectionBytestream: + def __init__(self): + self.files_props = {} + + def is_transfer_stoped(self, file_props): + if file_props.has_key('error') and file_props['error'] != 0: + return True + if file_props.has_key('completed') and file_props['completed']: + return True + if file_props.has_key('connected') and file_props['connected'] == False: + return True + if not file_props.has_key('stopped') or not file_props['stopped']: + return False + return True + + 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 remove_transfers_for_contact(self, contact): + ''' stop all active transfer for contact ''' + for file_props in self.files_props.values(): + if self.is_transfer_stoped(file_props): + continue + receiver_jid = unicode(file_props['receiver']).split('/')[0] + if contact.jid == receiver_jid: + file_props['error'] = -5 + self.remove_transfer(file_props) + self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props)) + sender_jid = unicode(file_props['sender']).split('/')[0] + if contact.jid == sender_jid: + file_props['error'] = -3 + self.remove_transfer(file_props) + + 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 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(',')) + default = gajim.proxy65_manager.get_default_for_name(self.name) + if default: + # add/move default proxy at top of the others + if proxies.__contains__(default): + proxies.remove(default) + proxies.insert(0, default) + + for proxy in proxies: + (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name) + 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 = helpers.get_auth_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 != [] and gajim.config.get_per('accounts', + self.name, 'use_ft_proxies'): + 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 send_file_rejection(self, file_props): + ''' informs sender that we refuse to download the file ''' + # user response to ConfirmationDialog may come after we've disconneted + if not self.connection or self.connected < 2: + return + 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): + ''' send iq, confirming that we want to download the file ''' + # user response to ConfirmationDialog may come after we've disconneted + if not self.connection or self.connected < 2: + return + 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) + if file_props.has_key('offset') and file_props['offset']: + file_tag = si.setTag('file') + file_tag.setNamespace(common.xmpp.NS_FILE) + 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): + ''' send iq for new FT request ''' + if not self.connection or self.connected < 2: + return + 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 _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 _connect_error(self, to, _id, sid, code = 404): + ''' cb, when there is an error establishing BS connection, or + when connection is rejected''' + 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 _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) + + # register xmpppy handlers for bytestream and FT stanzas + def _bytestreamErrorCB(self, con, iq_obj): + gajim.log.debug('_bytestreamErrorCB') + id = unicode(iq_obj.getAttr('id')) + frm = helpers.get_full_jid_from_iq(iq_obj) + query = iq_obj.getTag('query') + gajim.proxy65_manager.error_cb(frm, query) + jid = helpers.get_jid_from_iq(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': helpers.get_full_jid_from_iq(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 _ResultCB(self, con, iq_obj): + gajim.log.debug('_ResultCB') + # if we want to respect jep-0065 we have to check for proxy + # activation result in any result iq + real_id = unicode(iq_obj.getAttr('id')) + if real_id[:3] != 'au_': + return + frm = helpers.get_full_jid_from_iq(iq_obj) + id = real_id[3:] + if self.files_props.has_key(id): + file_props = self.files_props[id] + if file_props['streamhost-used']: + for host in file_props['proxyhosts']: + if host['initiator'] == frm and host.has_key('idx'): + gajim.socks5queue.activate_proxy(host['idx']) + raise common.xmpp.NodeProcessed + + def _bytestreamResultCB(self, con, iq_obj): + gajim.log.debug('_bytestreamResultCB') + frm = helpers.get_full_jid_from_iq(iq_obj) + real_id = unicode(iq_obj.getAttr('id')) + query = iq_obj.getTag('query') + gajim.proxy65_manager.resolve_result(frm, query) + + 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 _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 + if file_props.has_key('request-id'): + # we have already sent streamhosts info + return + file_props['receiver'] = helpers.get_full_jid_from_iq(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 _siSetCB(self, con, iq_obj): + gajim.log.debug('_siSetCB') + jid = helpers.get_jid_from_iq(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'] = helpers.get_full_jid_from_iq(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 = helpers.get_jid_from_iq(iq_obj) + file_props['error'] = -3 + self.dispatch('FILE_REQUEST_ERROR', (jid, file_props)) + raise common.xmpp.NodeProcessed + +class ConnectionDisco: + ''' hold xmpppy handlers and public methods for discover services''' + def discoverItems(self, jid, node = None, id_prefix = None): + '''According to JEP-0030: jid is mandatory, + name, node, action is optional.''' + self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix) + + def discoverInfo(self, jid, node = None, id_prefix = 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, id_prefix) + + 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[gajim.idlequeue.current_time() + 30] = (id, + _('Registration information for transport %s has not arrived in time' % \ + agent)) + self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo, + {'agent': agent}) + + 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 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 _discover(self, ns, jid, node = None, id_prefix = None): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = ns) + if id_prefix: + id = self.connection.getAnID() + iq.setID('%s%s' % (id_prefix, id)) + if node: + iq.setQuerynode(node) + self.connection.send(iq) + + def _ReceivedRegInfo(self, con, resp, agent): + common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent) + self._IqCB(con, resp) + + def _discoGetCB(self, con, iq_obj): + ''' get disco info ''' + frm = helpers.get_full_jid_from_iq(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 _DiscoverItemsErrorCB(self, con, iq_obj): + gajim.log.debug('DiscoverItemsErrorCB') + jid = helpers.get_full_jid_from_iq(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 = helpers.get_full_jid_from_iq(iq_obj) + hostname = gajim.config.get_per('accounts', self.name, + 'hostname') + id = iq_obj.getID() + if jid == hostname and id[0] == 'p': + for item in items: + self.discoverInfo(item['jid'], id_prefix='p') + else: + 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 = helpers.get_full_jid_from_iq(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 = helpers.get_full_jid_from_iq(iq_obj) + id = iq_obj.getID() + if not identities: # ejabberd doesn't send identities when we browse online users + #FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225 + identities = [{'category': 'server', 'type': 'im', 'name': node}] + if id[0] == 'p': + if features.__contains__(common.xmpp.NS_BYTESTREAM): + gajim.proxy65_manager.resolve(jid, self.connection, self.name) + self.dispatch('AGENT_INFO_INFO', (jid, node, identities, + features, data)) + +class ConnectionVcard: + def __init__(self): + self.vcard_sha = None + self.vcard_shas = {} # sha of contacts + self.room_jids = [] # list of gc jids so that vcard are saved in a folder + + def add_sha(self, p, send_caps = True): + c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE) + if self.vcard_sha is not None: + c.setTagData('photo', self.vcard_sha) + if send_caps: + return self.add_caps(p) + return p + + def add_caps(self, p): + ''' advertise our capabilities in presence stanza (jep-0115)''' + c = p.setTag('c', namespace = common.xmpp.NS_CAPS) + c.setAttr('node', 'http://gajim.org/caps') + c.setAttr('ext', 'ftrans') + c.setAttr('ver', gajim.version) + return p + + 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) + puny_jid = helpers.sanitize_filename(jid) + path = os.path.join(gajim.VCARD_PATH, puny_jid) + if jid in self.room_jids or os.path.isdir(path): + # 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 = helpers.sanitize_filename(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 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) + puny_jid = helpers.sanitize_filename(jid) + if is_fake_jid: + puny_nick = helpers.sanitize_filename(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) + our_jid = gajim.get_jid_from_account(self.name) + gajim.interface.save_avatar_files(our_jid, photo_decoded) + avatar_sha = sha.sha(photo_decoded).hexdigest() + iq2.getTag('PHOTO').setTagData('SHA', avatar_sha) + + self.awaiting_answers[id] = (VCARD_PUBLISHED, iq2) + + 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': + 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) + self.dispatch('VCARD_PUBLISHED', ()) + 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}) + elif self.awaiting_answers[id][0] == AGENT_REMOVED: + jid = self.awaiting_answers[id][1] + self.dispatch('AGENT_REMOVED', jid) + del self.awaiting_answers[id] + + 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 = helpers.get_full_jid_from_iq(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 = helpers.sanitize_filename(frm) + puny_nick = None + begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid) + if frm in self.room_jids: + puny_nick = helpers.sanitize_filename(resource) + # 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) + + +# TODO: remove bytestream and disco, vcard maybe later used? +class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco): + def __init__(self): + ConnectionVcard.__init__(self) + ConnectionBytestream.__init__(self) + # 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 = {} + # keep the jids we auto added (transports contacts) to not send the + # SUBSCRIBED event to gui + self.automatically_added = [] + try: + idle.init() + except: + HAS_IDLE = False + + 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 = helpers.get_full_jid_from_iq(iq_obj) + id = unicode(iq_obj.getID()) + self.dispatch('ERROR_ANSWER', (id, jid_from, errmsg, errcode)) + + 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 + print_status = conf.getTagData('print_status') + if not print_status: + print_status = conf.getTagData('show_status') + bm = {'name': conf.getAttr('name'), + 'jid': conf.getAttr('jid'), + 'autojoin': autojoin_val, + 'password': conf.getTagData('password'), + 'nick': conf.getTagData('nick'), + 'print_status': print_status} + + self.bookmarks.append(bm) + self.dispatch('BOOKMARKS', self.bookmarks) + + elif ns == 'storage:metacontacts': + # Metacontact tags + # http://www.jabber.org/jeps/jep-XXXX.html + meta_list = {} + metas = storage.getTags('meta') + for meta in metas: + jid = meta.getAttr('jid') + tag = meta.getAttr('tag') + data = {'jid': jid} + order = meta.getAttr('order') + if order != None: + data['order'] = order + if meta_list.has_key(tag): + meta_list[tag].append(data) + else: + meta_list[tag] = [data] + self.dispatch('METACONTACTS', meta_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') + storage_tag = query.getTag('storage') + if storage_tag: + ns = storage_tag.getNamespace() + if ns == 'storage:metacontacts': + # Private XML Storage (JEP49) is not supported by server + # Continue connecting + self.connection.initRoster() + + 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 _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', helpers.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 = helpers.get_full_jid_from_iq(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 = helpers.get_full_jid_from_iq(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 _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 = helpers.get_full_jid_from_iq(msg) + jid = helpers.get_jid_from_iq(msg) + no_log_for = gajim.config.get_per('accounts', self.name, + 'no_log_for').split() + 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 GnuPG.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': + error_msg = msg.getError() + if not error_msg: + error_msg = msgtxt + msgtxt = None + if self.name not in no_log_for: + gajim.logger.write('error', frm, error_msg, tim = tim, + subject = subject) + self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, 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] and msgtxt: + 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 and msgtxt: + msg_id = 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 and msgtxt: + 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) + who = helpers.get_full_jid_from_iq(prs) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + timestamp = None + is_gc = False # is it a GC presence ? + sigTag = None + avatar_sha = None + transport_auto_auth = False + 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') + if x.getNamespace() == common.xmpp.NS_DELAY: + # JEP-0091 + tim = prs.getTimestamp() + tim = time.strptime(tim, '%Y%m%dT%H:%M:%S') + timestamp = time.localtime(timegm(tim)) + if x.getNamespace() == 'http://delx.cjb.net/protocol/roster-subsync': + # see http://trac.gajim.org/ticket/326 + agent = gajim.get_server_from_jid(jid_stripped) + if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact + transport_auto_auth = True + + no_log_for = gajim.config.get_per('accounts', self.name, + 'no_log_for').split() + 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 GnuPG.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, timestamp)) + 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) + if avatar_sha: + if self.vcard_shas.has_key(who): + if avatar_sha != self.vcard_shas[who]: + # avatar has been updated + self.request_vcard(who, True) + else: + self.vcard_shas[who] = avatar_sha + 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 or transport_auto_auth: + if self.connection: + p = common.xmpp.Presence(who, 'subscribed') + p = self.add_sha(p) + self.connection.send(p) + if who.find("@") <= 0 or transport_auto_auth: + self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline', + resource, prio, keyID, timestamp)) + if transport_auto_auth: + self.automatically_added.append(jid_stripped) + self.request_subscription(jid_stripped) + else: + if not status: + status = _('I would like to add you to my roster.') + self.dispatch('SUBSCRIBE', (who, status)) + elif ptype == 'subscribed': + if jid_stripped in self.automatically_added: + self.automatically_added.remove(jid_stripped) + else: + 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, timestamp)) + else: # print in the window the error + self.dispatch('ERROR_ANSWER', ('', jid_stripped, + errmsg, errcode)) + + if avatar_sha and ptype != 'error': + if not self.vcard_shas.has_key(jid_stripped): + cached_vcard = self.get_cached_vcard(jid_stripped) + if cached_vcard and cached_vcard.has_key('PHOTO') and \ + cached_vcard['PHOTO'].has_key('SHA'): + self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA'] + else: + self.vcard_shas[jid_stripped] = '' + if avatar_sha != self.vcard_shas[jid_stripped]: + # avatar has been updated + self.request_vcard(jid_stripped) + 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, timestamp)) + # END presenceCB + def _StanzaArrivedCB(self, con, obj): + self.last_io = gajim.idlequeue.current_time() + + + 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', (helpers.get_full_jid_from_iq(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', (helpers.get_full_jid_from_iq(iq_obj), + affiliation, list)) + + def _MucErrorCB(self, con, iq_obj): + gajim.log.debug('MucErrorCB') + jid = helpers.get_full_jid_from_iq(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) + if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'): + self.discover_ft_proxies() + + def discover_ft_proxies(self): + cfg_proxies = gajim.config.get_per('accounts', self.name, + 'file_transfer_proxies') + if cfg_proxies: + proxies = map(lambda e:e.strip(), cfg_proxies.split(',')) + for proxy in proxies: + gajim.proxy65_manager.resolve(proxy, self.connection) + self.discoverItems(gajim.config.get_per('accounts', self.name, + 'hostname'), id_prefix='p') + + def _on_roster_set(self, roster): + raw_roster = roster.getRaw() + roster = {} + our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) + 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: + infos = raw_roster[jid] + if jid != our_jid and (not infos['subscription'] or infos['subscription'] == \ + 'none') and (not infos['ask'] or infos['ask'] == 'none') and not infos['name'] \ + and not infos['groups']: + # remove this useless item, it won't be shown in roster anyway + self.connection.getRoster().delItem(jid) + elif jid != our_jid: # don't add our jid + roster[j] = raw_roster[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 _register_handlers(self, con, con_type): + # try to find another way to register handlers in each class + # that defines handlers + 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('iq', self._ResultCB, 'result') + con.RegisterHandler('presence', self._StanzaArrivedCB) + con.RegisterHandler('message', self._StanzaArrivedCB) diff --git a/src/common/connection_zeroconf.py b/src/common/connection_zeroconf.py new file mode 100644 index 000000000..1126659c0 --- /dev/null +++ b/src/common/connection_zeroconf.py @@ -0,0 +1,1079 @@ +## common/connection.py +## +## Contributors for this file: +## - Yann Le Boulanger +## - Nikos Kouremenos +## - Dimitur Kirov +## - Travis Shirk +## +## Copyright (C) 2003-2004 Yann Le Boulanger +## Vincent Hanquez +## Copyright (C) 2005 Yann Le Boulanger +## Vincent Hanquez +## Nikos Kouremenos +## Dimitur Kirov +## Travis Shirk +## Norman Rasmussen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program 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. +## + + +import os +import random +random.seed() + +import signal +if os.name != 'nt': + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + +import common.xmpp +from common import helpers +from common import gajim +from common import GnuPG + +from connection_handlers_zeroconf import * +USE_GPG = GnuPG.USE_GPG + +from common import i18n +_ = i18n._ + +class ConnectionZeroconf(ConnectionHandlersZeroconf): + '''Connection class''' + def __init__(self, name): + ConnectionHandlersZeroconf.__init__(self) + self.name = name + self.connected = 0 # offline + self.connection = None # xmpppy ClientCommon instance + # this property is used to prevent double connections + self.last_connection = None # last ClientCommon instance + self.gpg = None + self.status = '' + self.old_show = '' + # increase/decrease default timeout for server responses + self.try_connecting_for_foo_secs = 45 + # holds the actual hostname to which we are connected + self.connected_hostname = None + self.time_to_reconnect = None + self.new_account_info = None + self.bookmarks = [] + self.on_purpose = False + self.last_io = gajim.idlequeue.current_time() + self.last_sent = [] + self.last_history_line = {} + self.password = gajim.config.get_per('accounts', name, 'password') + self.server_resource = gajim.config.get_per('accounts', name, 'resource') + if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'): + self.keepalives = gajim.config.get_per('accounts', self.name,'keep_alive_every_foo_secs') + else: + self.keepalives = 0 + self.privacy_rules_supported = False + # Do we continue connection when we get roster (send presence,get vcard...) + self.continue_connect_info = None + if USE_GPG: + self.gpg = GnuPG.GnuPG() + gajim.config.set('usegpg', True) + else: + gajim.config.set('usegpg', False) + + self.on_connect_success = None + self.on_connect_failure = None + self.retrycount = 0 + self.jids_for_auto_auth = [] # list of jid to auto-authorize + + # END __init__ + def put_event(self, ev): + if gajim.handlers.has_key(ev[0]): + gajim.handlers[ev[0]](self.name, ev[1]) + + def dispatch(self, event, data): + '''always passes account name as first param''' + self.put_event((event, data)) + + + def _reconnect(self): + # Do not try to reco while we are already trying +''' self.time_to_reconnect = None + if self.connected < 2: #connection failed + gajim.log.debug('reconnect') + self.retrycount += 1 + signed = self.get_signed_msg(self.status) + self.on_connect_auth = self._init_roster + self.connect_and_init(self.old_show, self.status, signed) + else: + # reconnect succeeded + self.time_to_reconnect = None + self.retrycount = 0 ''' + + self.time_to_reconnect = None + self.retrycount = 0 + gajim.log.debug('reconnect') + signed = self.get_signed_msg(self.status) + + + # We are doing disconnect at so many places, better use one function in all + def disconnect(self, on_purpose = False): + self.on_purpose = on_purpose + self.connected = 0 + self.time_to_reconnect = None + if self.connection: + # make sure previous connection is completely closed + gajim.proxy65_manager.disconnect(self.connection) + self.connection.disconnect() + self.last_connection = None + self.connection = None + + def _disconnectedReconnCB(self): + '''Called when we are disconnected''' + gajim.log.debug('disconnectedReconnCB') + if self.connected > 1: + # we cannot change our status to offline or connectiong + # after we auth to server + self.old_show = STATUS_LIST[self.connected] + self.connected = 0 + self.dispatch('STATUS', 'offline') + if not self.on_purpose: + self.disconnect() + if gajim.config.get_per('accounts', self.name, 'autoreconnect') \ + and self.retrycount <= 10: + self.connected = 1 + self.dispatch('STATUS', 'connecting') + self.time_to_reconnect = 10 + # this check has moved from _reconnect method + if self.retrycount > 5: + self.time_to_reconnect = 20 + else: + self.time_to_reconnect = 10 + gajim.idlequeue.set_alarm(self._reconnect_alarm, + self.time_to_reconnect) + elif self.on_connect_failure: + self.on_connect_failure() + self.on_connect_failure = None + else: + # show error dialog + self._connection_lost() + else: + self.disconnect() + self.on_purpose = False + # END disconenctedReconnCB + + def _connection_lost(self): + self.disconnect(on_purpose = False) + self.dispatch('STATUS', 'offline') + self.dispatch('ERROR', + (_('Connection with account "%s" has been lost') % self.name, + _('To continue sending and receiving messages, you will need to reconnect.'))) + + def _event_dispatcher(self, realm, event, data): + if realm == common.xmpp.NS_REGISTER: + if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED: + # data is (agent, DataFrom, is_form) + if self.new_account_info and\ + self.new_account_info['hostname'] == data[0]: + #it's a new account + if not data[1]: # wrong answer + print self.connection.lastErr + self.dispatch('ACC_NOT_OK', ( + _('Transport %s answered wrongly to register request.') % \ + data[0])) + return + req = data[1].asDict() + req['username'] = self.new_account_info['name'] + req['password'] = self.new_account_info['password'] + def _on_register_result(result): + if not common.xmpp.isResultNode(result): + self.dispatch('ACC_NOT_OK', (result.getError())) + return + self.connected = 0 + self.password = self.new_account_info['password'] + if USE_GPG: + self.gpg = GnuPG.GnuPG() + gajim.config.set('usegpg', True) + else: + gajim.config.set('usegpg', False) + gajim.connections[self.name] = self + self.dispatch('ACC_OK', (self.new_account_info)) + self.new_account_info = None + self.connection = None + common.xmpp.features_nb.register(self.connection, data[0], + req, _on_register_result) + return + if not data[1]: # wrong answer + self.dispatch('ERROR', (_('Invalid answer'), + _('Transport %s answered wrongly to register request.') % \ + data[0])) + return + is_form = data[2] + if is_form: + conf = self.parse_data_form(data[1]) + else: + conf = data[1].asDict() + self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form)) + elif realm == '': + if event == common.xmpp.transports.DATA_RECEIVED: + self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore')) + elif event == common.xmpp.transports.DATA_SENT: + self.dispatch('STANZA_SENT', unicode(data)) + + def select_next_host(self, hosts): + hosts_best_prio = [] + best_prio = 65535 + sum_weight = 0 + for h in hosts: + if h['prio'] < best_prio: + hosts_best_prio = [h] + best_prio = h['prio'] + sum_weight = h['weight'] + elif h['prio'] == best_prio: + hosts_best_prio.append(h) + sum_weight += h['weight'] + if len(hosts_best_prio) == 1: + return hosts_best_prio[0] + r = random.randint(0, sum_weight) + min_w = sum_weight + # We return the one for which has the minimum weight and weight >= r + for h in hosts_best_prio: + if h['weight'] >= r: + if h['weight'] <= min_w: + min_w = h['weight'] + return h + + def connect(self, data = None): + # TODO: remove any server connection and get zeroconf instance instead + # get presence information from there + + ''' Start a connection to the Jabber server. + Returns connection, and connection type ('tls', 'ssl', 'tcp', '') + data MUST contain name, hostname, resource, usessl, proxy, + use_custom_host, custom_host (if use_custom_host), custom_port (if + use_custom_host), ''' + if self.connection: + return self.connection, '' + + if data: + name = data['name'] + hostname = data['hostname'] + resource = data['resource'] + usessl = data['usessl'] + self.try_connecting_for_foo_secs = 45 + p = data['proxy'] + use_srv = True + use_custom = data['use_custom_host'] + if use_custom: + custom_h = data['custom_host'] + custom_p = data['custom_port'] + else: + 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') + usessl = gajim.config.get_per('accounts', self.name, 'usessl') + self.try_connecting_for_foo_secs = gajim.config.get_per('accounts', + self.name, 'try_connecting_for_foo_secs') + p = gajim.config.get_per('accounts', self.name, 'proxy') + use_srv = gajim.config.get_per('accounts', self.name, 'use_srv') + use_custom = gajim.config.get_per('accounts', self.name, + 'use_custom_host') + custom_h = gajim.config.get_per('accounts', self.name, 'custom_host') + custom_p = gajim.config.get_per('accounts', self.name, 'custom_port') + + #create connection if it doesn't already exist + self.connected = 1 + if p and p in gajim.config.get_per('proxies'): + proxy = {'host': gajim.config.get_per('proxies', p, 'host')} + proxy['port'] = gajim.config.get_per('proxies', p, 'port') + proxy['user'] = gajim.config.get_per('proxies', p, 'user') + proxy['password'] = gajim.config.get_per('proxies', p, 'pass') + else: + proxy = None + + h = hostname + p = 5222 + # autodetect [for SSL in 5223/443 and for TLS if broadcasted] + secur = None + if usessl: + p = 5223 + secur = 1 # 1 means force SSL no matter what the port will be + use_srv = False # wants ssl? disable srv lookup + if use_custom: + h = custom_h + p = custom_p + use_srv = False + + hosts = [] + # SRV resolver + self._proxy = proxy + self._secure = secur + self._hosts = [ {'host': h, 'port': p, 'prio': 10, 'weight': 10} ] + self._hostname = hostname + if use_srv: + # add request for srv query to the resolve, on result '_on_resolve' will be called + gajim.resolver.resolve('_xmpp-client._tcp.' + h.encode('utf-8'), self._on_resolve) + else: + self._on_resolve('', []) + + def _on_resolve(self, host, result_array): + # SRV query returned at least one valid result, we put it in hosts dict + if len(result_array) != 0: + self._hosts = [i for i in result_array] + self.connect_to_next_host() + + def connect_to_next_host(self, retry = False): + if len(self._hosts): + if self.last_connection: + self.last_connection.socket.disconnect() + self.last_connection = None + self.connection = None + if gajim.verbose: + con = common.xmpp.NonBlockingClient(self._hostname, caller = self, + on_connect = self.on_connect_success, + on_connect_failure = self.connect_to_next_host) + else: + con = common.xmpp.NonBlockingClient(self._hostname, debug = [], caller = self, + on_connect = self.on_connect_success, + on_connect_failure = self.connect_to_next_host) + self.last_connection = con + # increase default timeout for server responses + common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs + con.set_idlequeue(gajim.idlequeue) + host = self.select_next_host(self._hosts) + self._current_host = host + self._hosts.remove(host) + con.connect((host['host'], host['port']), proxy = self._proxy, + secure = self._secure) + return + else: + if not retry or self.retrycount > 10: + self.retrycount = 0 + self.time_to_reconnect = None + if self.on_connect_failure: + self.on_connect_failure() + self.on_connect_failure = None + else: + # shown error dialog + self._connection_lost() + else: + # try reconnect if connection has failed before auth to server + self._disconnectedReconnCB() + + def _connect_failure(self, con_type = None): + if not con_type: + # we are not retrying, and not conecting + if not self.retrycount and self.connected != 0: + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname, + _('Check your connection or try again later.'))) + + def _connect_success(self, con, con_type): + if not self.connected: # We went offline during connecting process + # FIXME - not possible, maybe it was when we used threads + return + self.hosts = [] + if not con_type: + gajim.log.debug('Could not connect to %s:%s' % (self._current_host['host'], + self._current_host['port'])) + self.connected_hostname = self._current_host['host'] + self.on_connect_failure = None + con.RegisterDisconnectHandler(self._disconnectedReconnCB) + gajim.log.debug(_('Connected to server %s:%s with %s') % (self._current_host['host'], + self._current_host['port'], con_type)) + # Ask metacontacts before roster + self.get_metacontacts() + self._register_handlers(con, con_type) + return True + + def _register_handlers(self, con, con_type): + self.peerhost = con.get_peerhost() + # notify the gui about con_type + self.dispatch('CON_TYPE', con_type) + 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') + self.connection = con + con.auth(name, self.password, resource, 1, self.__on_auth) + + def __on_auth(self, con, auth): + if not con: + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname, + _('Check your connection or try again later'))) + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + return + if not self.connected: # We went offline during connecting process + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + return + if hasattr(con, 'Resource'): + self.server_resource = con.Resource + if auth: + self.last_io = gajim.idlequeue.current_time() + self.connected = 2 + if self.on_connect_auth: + self.on_connect_auth(con) + self.on_connect_auth = None + else: + # Forget password if needed + if not gajim.config.get_per('accounts', self.name, 'savepass'): + self.password = None + gajim.log.debug("Couldn't authenticate to %s" % self._hostname) + self.disconnect(on_purpose = True) + self.dispatch('STATUS', 'offline') + self.dispatch('ERROR', (_('Authentication failed with "%s"') % self._hostname, + _('Please check your login and password for correctness.'))) + if self.on_connect_auth: + self.on_connect_auth(None) + self.on_connect_auth = None + # END connect + + def quit(self, kill_core): + if kill_core and self.connected > 1: + self.disconnect(on_purpose = True) + + def build_privacy_rule(self, name, action): + '''Build a Privacy rule stanza for invisibility''' + iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') + l = iq.getTag('query').setTag('list', {'name': name}) + i = l.setTag('item', {'action': action, 'order': '1'}) + i.setTag('presence-out') + return iq + + def activate_privacy_rule(self, name): + '''activate a privacy rule''' + iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') + iq.getTag('query').setTag('active', {'name': name}) + self.connection.send(iq) + + def send_invisible_presence(self, msg, signed, initial = False): + # try to set the privacy rule + iq = self.build_privacy_rule('invisible', 'deny') + self.connection.SendAndCallForResponse(iq, self._continue_invisible, + {'msg': msg, 'signed': signed, 'initial': initial}) + + def _continue_invisible(self, con, iq_obj, msg, signed, initial): + ptype = '' + show = '' + # FIXME: JEP 126 need some modifications (see http://lists.jabber.ru/pipermail/ejabberd/2005-July/001252.html). So I disable it for the moment + if 1 or iq_obj.getType() == 'error': #server doesn't support privacy lists + # We use the old way which is not xmpp complient + ptype = 'invisible' + show = 'invisible' + else: + # active the privacy rule + self.privacy_rules_supported = True + self.activate_privacy_rule('invisible') + prio = unicode(gajim.config.get_per('accounts', self.name, 'priority')) + p = common.xmpp.Presence(typ = ptype, priority = prio, show = show) + p = self.add_sha(p, ptype != 'unavailable') + if msg: + p.setStatus(msg) + if signed: + p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) + self.connection.send(p) + self.dispatch('STATUS', 'invisible') + if initial: + #ask our VCard + self.request_vcard(None) + + #Get bookmarks from private namespace + self.get_bookmarks() + + #Inform GUI we just signed in + self.dispatch('SIGNED_IN', ()) + + def test_gpg_passphrase(self, password): + self.gpg.passphrase = password + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + signed = self.gpg.sign('test', keyID) + self.gpg.password = None + return signed != 'BAD_PASSPHRASE' + + def get_signed_msg(self, msg): + signed = '' + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + if keyID and USE_GPG: + use_gpg_agent = gajim.config.get('use_gpg_agent') + if self.connected < 2 and self.gpg.passphrase is None and \ + not use_gpg_agent: + # We didn't set a passphrase + self.dispatch('ERROR', (_('OpenPGP passphrase was not given'), + #%s is the account name here + _('You will be connected to %s without OpenPGP.') % self.name)) + elif self.gpg.passphrase is not None or use_gpg_agent: + signed = self.gpg.sign(msg, keyID) + if signed == 'BAD_PASSPHRASE': + signed = '' + if self.connected < 2: + self.dispatch('BAD_PASSPHRASE', ()) + return signed + + def connect_and_auth(self): + self.on_connect_success = self._connect_success + self.on_connect_failure = self._connect_failure + self.connect() + + def connect_and_init(self, show, msg, signed): + self.continue_connect_info = [show, msg, signed] + self.on_connect_auth = self._init_roster + self.connect_and_auth() + + def _init_roster(self, con): + self.connection = con + if self.connection: + con.set_send_timeout(self.keepalives, self.send_keepalive) + self.connection.onreceive(None) + # Ask metacontacts before roster + self.get_metacontacts() + + def change_status(self, show, msg, sync = False, auto = False): + if not show in STATUS_LIST: + return -1 + sshow = helpers.get_xmpp_show(show) + if not msg: + msg = '' + keyID = gajim.config.get_per('accounts', self.name, 'keyid') + if keyID and USE_GPG and not msg: + lowered_uf_status_msg = helpers.get_uf_show(show).lower() + # do not show I'm invisible! + if lowered_uf_status_msg == _('invisible'): + lowered_uf_status_msg = _('offline') + msg = _("I'm %s") % lowered_uf_status_msg + signed = '' + if not auto and not show == 'offline': + signed = self.get_signed_msg(msg) + self.status = msg + if show != 'offline' and not self.connected: + # set old_show to requested 'show' in case we need to + # recconect before we auth to server + self.old_show = show + self.on_purpose = False + self.connect_and_init(show, msg, signed) + + elif show == 'offline' and self.connected: + self.connected = 0 + if self.connection: + self.on_purpose = True + p = common.xmpp.Presence(typ = 'unavailable') + p = self.add_sha(p, False) + if msg: + p.setStatus(msg) + self.remove_all_transfers() + self.time_to_reconnect = None + self.connection.start_disconnect(p, self._on_disconnected) + else: + self.time_to_reconnect = None + self._on_disconnected() + + elif show != 'offline' and self.connected: + # dont'try to connect, when we are in state 'connecting' + if self.connected == 1: + return + was_invisible = self.connected == STATUS_LIST.index('invisible') + self.connected = STATUS_LIST.index(show) + if show == 'invisible': + self.send_invisible_presence(msg, signed) + return + if was_invisible and self.privacy_rules_supported: + iq = self.build_privacy_rule('visible', 'allow') + self.connection.send(iq) + self.activate_privacy_rule('visible') + prio = unicode(gajim.config.get_per('accounts', self.name, + 'priority')) + 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) + + def _on_disconnected(self): + ''' called when a disconnect request has completed successfully''' + self.dispatch('STATUS', 'offline') + self.disconnect() + + def get_status(self): + return STATUS_LIST[self.connected] + + def send_motd(self, jid, subject = '', msg = ''): + if not self.connection: + return + msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject) + self.connection.send(msg_iq) + + def send_message(self, jid, msg, keyID, type = 'chat', subject='', + chatstate = None, msg_id = None, composing_jep = None, resource = None): + if not self.connection: + return + if not msg and chatstate is None: + return + fjid = jid + if resource: + fjid += '/' + resource + msgtxt = msg + msgenc = '' + if keyID and USE_GPG: + #encrypt + msgenc = self.gpg.encrypt(msg, [keyID]) + if msgenc: + msgtxt = '[This message is encrypted]' + lang = os.getenv('LANG') + if lang is not None or lang != 'en': # we're not english + msgtxt = _('[This message is encrypted]') +\ + ' ([This message is encrypted])' # one in locale and one en + if type == 'chat': + msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type) + else: + if subject: + msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, + typ = 'normal', subject = subject) + else: + msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, + typ = 'normal') + if msgenc: + msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) + + # chatstates - if peer supports jep85 or jep22, send chatstates + # please note that the only valid tag inside a message containing a + # tag is the active event + if chatstate is not None: + if composing_jep == 'JEP-0085' or not composing_jep: + # JEP-0085 + msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES) + if composing_jep == 'JEP-0022' or not composing_jep: + # JEP-0022 + chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT) + if not msgtxt: # when no , add + if not msg_id: # avoid putting 'None' in tag + msg_id = '' + chatstate_node.setTagData('id', msg_id) + # when msgtxt, requests JEP-0022 composing notification + if chatstate is 'composing' or msgtxt: + chatstate_node.addChild(name = 'composing') + + self.connection.send(msg_iq) + no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for') + ji = gajim.get_jid_without_resource(jid) + if self.name not in no_log_for and ji not in no_log_for: + log_msg = msg + if subject: + log_msg = _('Subject: %s\n%s') % (subject, msg) + if log_msg: + if type == 'chat': + kind = 'chat_msg_sent' + else: + kind = 'single_msg_sent' + 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 + gajim.log.debug('ack\'ing subscription complete for %s' % jid) + p = common.xmpp.Presence(jid, 'subscribe') + self.connection.send(p) + + def ack_unsubscribed(self, jid): + if not self.connection: + return + gajim.log.debug('ack\'ing unsubscription complete for %s' % jid) + p = common.xmpp.Presence(jid, 'unsubscribe') + self.connection.send(p) + + def request_subscription(self, jid, msg = '', name = '', groups = [], + auto_auth = False): + if not self.connection: + return + gajim.log.debug('subscription request for %s' % jid) + if auto_auth: + self.jids_for_auto_auth.append(jid) + # RFC 3921 section 8.2 + infos = {'jid': jid} + if name: + infos['name'] = name + iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER) + q = iq.getTag('query') + item = q.addChild('item', attrs = infos) + for g in groups: + item.addChild('group').setData(g) + self.connection.send(iq) + + p = common.xmpp.Presence(jid, 'subscribe') + p = self.add_sha(p) + if not msg: + msg = _('I would like to add you to my roster.') + p.setStatus(msg) + self.connection.send(p) + + def send_authorization(self, jid): + if not self.connection: + return + p = common.xmpp.Presence(jid, 'subscribed') + p = self.add_sha(p) + self.connection.send(p) + + def refuse_authorization(self, jid): + if not self.connection: + return + p = common.xmpp.Presence(jid, 'unsubscribed') + p = self.add_sha(p) + self.connection.send(p) + + def unsubscribe(self, jid, remove_auth = True): + if not self.connection: + return + if remove_auth: + self.connection.getRoster().delItem(jid) + jid_list = gajim.config.get_per('contacts') + for j in jid_list: + if j.startswith(jid): + gajim.config.del_per('contacts', j) + else: + self.connection.getRoster().Unsubscribe(jid) + self.update_contact(jid, '', []) + + def unsubscribe_agent(self, agent): + if not self.connection: + return + iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) + iq.getTag('query').setTag('remove') + id = self.connection.getAnID() + iq.setID(id) + self.awaiting_answers[id] = (AGENT_REMOVED, agent) + self.connection.send(iq) + self.connection.getRoster().delItem(agent) + + def update_contact(self, jid, name, groups): + '''update roster item on jabber server''' + if self.connection: + self.connection.getRoster().setItem(jid = jid, name = name, + groups = groups) + + def new_account(self, name, config, sync = False): + # If a connection already exist we cannot create a new account + if self.connection: + return + self._hostname = config['hostname'] + self.new_account_info = config + self.name = name + self.on_connect_success = self._on_new_account + self.on_connect_failure = self._on_new_account + self.connect(config) + + def _on_new_account(self, con = None, con_type = None): + if not con_type: + self.dispatch('ACC_NOT_OK', + (_('Could not connect to "%s"') % self._hostname)) + return + self.on_connect_failure = None + self.connection = con + common.xmpp.features_nb.getRegInfo(con, self._hostname) + + def account_changed(self, new_name): + self.name = new_name + + def request_last_status_time(self, jid, resource): + if not self.connection: + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\ + common.xmpp.NS_LAST) + self.connection.send(iq) + + def request_os_info(self, jid, resource): + if not self.connection: + return + to_whom_jid = jid + if resource: + to_whom_jid += '/' + resource + iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\ + common.xmpp.NS_VERSION) + self.connection.send(iq) + + def get_settings(self): + ''' Get Gajim settings as described in JEP 0049 ''' + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace='jabber:iq:private') + iq3 = iq2.addChild(name='gajim', namespace='gajim:prefs') + self.connection.send(iq) + + def get_bookmarks(self): + '''Get Bookmarks from storage as described in JEP 0048''' + self.bookmarks = [] #avoid multiple bookmarks when re-connecting + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace='jabber:iq:private') + iq2.addChild(name='storage', namespace='storage:bookmarks') + self.connection.send(iq) + + def store_bookmarks(self): + ''' Send bookmarks to the storage namespace ''' + if not self.connection: + return + iq = common.xmpp.Iq(typ='set') + iq2 = iq.addChild(name='query', namespace='jabber:iq:private') + iq3 = iq2.addChild(name='storage', namespace='storage:bookmarks') + for bm in self.bookmarks: + iq4 = iq3.addChild(name = "conference") + iq4.setAttr('jid', bm['jid']) + iq4.setAttr('autojoin', bm['autojoin']) + iq4.setAttr('name', bm['name']) + # Only add optional elements if not empty + # Note: need to handle both None and '' as empty + # thus shouldn't use "is not None" + if bm['nick']: + iq5 = iq4.setTagData('nick', bm['nick']) + if bm['password']: + iq5 = iq4.setTagData('password', bm['password']) + if bm['print_status']: + iq5 = iq4.setTagData('print_status', bm['print_status']) + self.connection.send(iq) + + def get_metacontacts(self): + '''Get metacontacts list from storage as described in JEP 0049''' + if not self.connection: + return + iq = common.xmpp.Iq(typ='get') + iq2 = iq.addChild(name='query', namespace='jabber:iq:private') + iq2.addChild(name='storage', namespace='storage:metacontacts') + self.connection.send(iq) + + def store_metacontacts(self, tags_list): + ''' Send meta contacts to the storage namespace ''' + if not self.connection: + return + iq = common.xmpp.Iq(typ='set') + iq2 = iq.addChild(name='query', namespace='jabber:iq:private') + iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts') + for tag in tags_list: + for data in tags_list[tag]: + jid = data['jid'] + dict_ = {'jid': jid, 'tag': tag} + if data.has_key('order'): + dict_['order'] = data['order'] + iq3.addChild(name = 'meta', attrs = dict_) + self.connection.send(iq) + + def send_agent_status(self, agent, ptype): + if not self.connection: + return + p = common.xmpp.Presence(to = agent, typ = ptype) + p = self.add_sha(p, ptype != 'unavailable') + self.connection.send(p) + + def join_gc(self, nick, room, server, password): + if not self.connection: + return + show = helpers.get_xmpp_show(STATUS_LIST[self.connected]) + if show == 'invisible': + # Never join a room when invisible + return + p = common.xmpp.Presence(to = '%s@%s/%s' % (room, server, nick), + show = show, status = self.status) + if gajim.config.get('send_sha_in_gc_presence'): + p = self.add_sha(p) + t = p.setTag(common.xmpp.NS_MUC + ' x') + if password: + t.setTagData('password', password) + self.connection.send(p) + #last date/time in history to avoid duplicate + # FIXME: This JID needs to be normalized; see #1364 + jid='%s@%s' % (room, server) + last_log = gajim.logger.get_last_date_that_has_logs(jid, is_room = True) + if last_log is None: + last_log = 0 + self.last_history_line[jid]= last_log + + def send_gc_message(self, jid, msg): + if not self.connection: + return + msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat') + self.connection.send(msg_iq) + self.dispatch('MSGSENT', (jid, msg)) + + def send_gc_subject(self, jid, subject): + if not self.connection: + return + msg_iq = common.xmpp.Message(jid,typ = 'groupchat', subject = subject) + self.connection.send(msg_iq) + + def request_gc_config(self, room_jid): + iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER, + to = room_jid) + self.connection.send(iq) + + def change_gc_nick(self, room_jid, nick): + if not self.connection: + return + p = common.xmpp.Presence(to = '%s/%s' % (room_jid, nick)) + p = self.add_sha(p) + self.connection.send(p) + + def send_gc_status(self, nick, jid, show, status): + if not self.connection: + return + if show == 'invisible': + show = 'offline' + ptype = None + if show == 'offline': + ptype = 'unavailable' + show = helpers.get_xmpp_show(show) + p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype, + show = show, status = status) + if gajim.config.get('send_sha_in_gc_presence'): + p = self.add_sha(p, ptype != 'unavailable') + # send instantly so when we go offline, status is sent to gc before we + # disconnect from jabber server + self.connection.send(p) + + def gc_set_role(self, room_jid, nick, role, reason = ''): + '''role is for all the life of the room so it's based on nick''' + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('nick', nick) + item.setAttr('role', role) + if reason: + item.addChild(name = 'reason', payload = reason) + self.connection.send(iq) + + def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''): + '''affiliation is for all the life of the room so it's based on jid''' + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('jid', jid) + item.setAttr('affiliation', affiliation) + if reason: + item.addChild(name = 'reason', payload = reason) + self.connection.send(iq) + + def send_gc_affiliation_list(self, room_jid, list): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query') + for jid in list: + item_tag = item.addChild('item', {'jid': jid, + 'affiliation': list[jid]['affiliation']}) + if list[jid].has_key('reason') and list[jid]['reason']: + item_tag.setTagData('reason', list[jid]['reason']) + self.connection.send(iq) + + def get_affiliation_list(self, room_jid, affiliation): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \ + common.xmpp.NS_MUC_ADMIN) + item = iq.getTag('query').setTag('item') + item.setAttr('affiliation', affiliation) + self.connection.send(iq) + + def send_gc_config(self, room_jid, config): + iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ + common.xmpp.NS_MUC_OWNER) + query = iq.getTag('query') + self.build_data_from_dict(query, config) + self.connection.send(iq) + + def gpg_passphrase(self, passphrase): + if USE_GPG: + use_gpg_agent = gajim.config.get('use_gpg_agent') + if use_gpg_agent: + self.gpg.passphrase = None + else: + self.gpg.passphrase = passphrase + + def ask_gpg_keys(self): + if USE_GPG: + keys = self.gpg.get_keys() + return keys + return None + + def ask_gpg_secrete_keys(self): + if USE_GPG: + keys = self.gpg.get_secret_keys() + return keys + return None + + def change_password(self, password): + if not self.connection: + return + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + username = gajim.config.get_per('accounts', self.name, 'name') + iq = common.xmpp.Iq(typ = 'set', to = hostname) + q = iq.setTag(common.xmpp.NS_REGISTER + ' query') + q.setTagData('username',username) + q.setTagData('password',password) + self.connection.send(iq) + + def unregister_account(self, on_remove_success): + # no need to write this as a class method and keep the value of on_remove_success + # as a class property as pass it as an argument + def _on_unregister_account_connect(con): + self.on_connect_auth = None + if self.connected > 1: + hostname = gajim.config.get_per('accounts', self.name, 'hostname') + iq = common.xmpp.Iq(typ = 'set', to = hostname) + q = iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove') + con.send(iq) + on_remove_success(True) + return + on_remove_success(False) + if self.connected == 0: + self.on_connect_auth = _on_unregister_account_connect + self.connect_and_auth() + else: + _on_unregister_account_connect(self.connection) + + def send_invite(self, room, to, reason=''): + '''sends invitation''' + message=common.xmpp.Message(to = room) + c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER) + c = c.addChild(name = 'invite', attrs={'to' : to}) + if reason != '': + c.setTagData('reason', reason) + self.connection.send(message) + + def send_keepalive(self): + # nothing received for the last foo seconds (60 secs by default) + if self.connection: + self.connection.send(' ') + + def _reconnect_alarm(self): + if self.time_to_reconnect: + if self.connected < 2: + self._reconnect() + else: + self.time_to_reconnect = None + +# END Connection