# -*- coding:utf-8 -*- ## src/common/connection_handlers_events.py ## ## Copyright (C) 2010-2014 Yann Leboulanger ## ## This file is part of Gajim. ## ## Gajim 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 3 only. ## ## Gajim is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Gajim. If not, see . ## # pylint: disable=no-init # pylint: disable=attribute-defined-outside-init from calendar import timegm import hashlib import hmac import logging import sys from time import time as time_time import OpenSSL.crypto import nbxmpp from nbxmpp.protocol import NS_CHATSTATES from gajim.common import nec from gajim.common import helpers from gajim.common import app from gajim.common import i18n from gajim.common import dataforms from gajim.common.zeroconf.zeroconf import Constant from gajim.common.const import KindConstant, SSLError from gajim.common.pep import SUPPORTED_PERSONAL_USER_EVENTS from gajim.common.jingle_transport import JingleTransportSocks5 from gajim.common.file_props import FilesProp from gajim.common.nec import NetworkEvent log = logging.getLogger('gajim.c.connection_handlers_events') CONDITION_TO_CODE = { 'realjid-public': 100, 'affiliation-changed': 101, 'unavailable-shown': 102, 'unavailable-not-shown': 103, 'configuration-changed': 104, 'self-presence': 110, 'logging-enabled': 170, 'logging-disabled': 171, 'non-anonymous': 172, 'semi-anonymous': 173, 'fully-anonymous': 174, 'room-created': 201, 'nick-assigned': 210, 'banned': 301, 'new-nick': 303, 'kicked': 307, 'removed-affiliation': 321, 'removed-membership': 322, 'removed-shutdown': 332, } class HelperEvent: def get_jid_resource(self, check_fake_jid=False): if check_fake_jid and hasattr(self, 'id_') and \ self.id_ in self.conn.groupchat_jids: self.fjid = self.conn.groupchat_jids[self.id_] del self.conn.groupchat_jids[self.id_] else: self.fjid = helpers.get_full_jid_from_iq(self.stanza) self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid) def get_id(self): self.id_ = self.stanza.getID() def get_gc_control(self): self.gc_control = app.interface.msg_win_mgr.get_gc_control(self.jid, self.conn.name) # If gc_control is missing - it may be minimized. Try to get it # from there. If it's not there - then it's missing anyway and # will remain set to None. if not self.gc_control: minimized = app.interface.minimized_controls[self.conn.name] self.gc_control = minimized.get(self.jid) def _generate_timestamp(self, tag): # Make sure we use only int/float Epoch time if tag is None: self.timestamp = time_time() return try: tim = helpers.datetime_tuple(tag) self.timestamp = timegm(tim) except Exception: log.error('wrong timestamp, ignoring it: ' + tag) self.timestamp = time_time() def get_chatstate(self): """ Extract chatstate from a stanza Requires self.stanza and self.msgtxt """ self.chatstate = None # chatstates - look for chatstate tags in a message if not delayed delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not None if not delayed: children = self.stanza.getChildren() for child in children: if child.getNamespace() == NS_CHATSTATES: self.chatstate = child.getName() break def get_oob_data(self, stanza): oob_node = stanza.getTag('x', namespace=nbxmpp.NS_X_OOB) if oob_node is not None: if 'gajim' not in self.additional_data: self.additional_data['gajim'] = {} oob_url = oob_node.getTagData('url') if oob_url is not None: self.additional_data['gajim']['oob_url'] = oob_url oob_desc = oob_node.getTagData('desc') if oob_desc is not None: self.additional_data['gajim']['oob_desc'] = oob_desc def get_stanza_id(self, stanza, query=False): if query: # On a MAM query the stanza-id is maybe not set, so # get the id of the stanza return stanza.getAttr('id') stanza_id, by = stanza.getStanzaIDAttrs() if by is None: # We can not verify who set this stanza-id, ignore it. return if stanza.getType() == 'groupchat': if stanza.getFrom().bareMatch(by): # by attribute must match the server return stanza_id elif self.conn.get_own_jid().bareMatch(by): # by attribute must match the server return stanza_id return @staticmethod def get_forwarded_message(stanza): forwarded = stanza.getTag('forwarded', namespace=nbxmpp.NS_FORWARD, protocol=True) if forwarded is not None: return forwarded.getTag('message', protocol=True) def _is_self_message(self, message): if self.self_message is not None: return self.self_message own_jid = self.conn.get_own_jid() frm = message.getFrom() to = message.getTo() # If 'to' is not set we assume own jid self.self_message = frm.bareMatch(to or own_jid) return self.self_message def _is_muc_pm(self, message): if self.muc_pm is not None: return self.muc_pm self.muc_pm = False muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER) if muc_user is not None: self.muc_pm = muc_user.getChildren() == [] return self.muc_pm class RosterReceivedEvent(nec.NetworkIncomingEvent): name = 'roster-received' base_network_events = [] def generate(self): if hasattr(self, 'xmpp_roster'): self.version = self.xmpp_roster.version self.received_from_server = self.xmpp_roster.received_from_server self.roster = {} raw_roster = self.xmpp_roster.getRaw() our_jid = app.get_jid_from_account(self.conn.name) for jid in raw_roster: try: j = helpers.parse_jid(jid) except Exception: print(_('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, file=sys.stderr) 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.conn.connection.getRoster().delItem(jid) elif jid != our_jid: # don't add our jid self.roster[j] = raw_roster[jid] self.roster[j]['avatar_sha'] = None else: # Roster comes from DB self.received_from_server = False self.version = app.config.get_per('accounts', self.conn.name, 'roster_version') self.roster = app.logger.get_roster(app.get_jid_from_account( self.conn.name)) if not self.roster: app.config.set_per( 'accounts', self.conn.name, 'roster_version', '') return True class RosterSetReceivedEvent(nec.NetworkIncomingEvent): name = 'roster-set-received' base_network_events = [] def generate(self): frm = helpers.get_jid_from_iq(self.stanza) our_jid = app.get_jid_from_account(self.conn.name) if frm and frm != our_jid and frm != app.get_server_from_jid(our_jid): return self.version = self.stanza.getTagAttr('query', 'ver') self.items = {} for item in self.stanza.getTag('query').getChildren(): try: jid = helpers.parse_jid(item.getAttr('jid')) except helpers.InvalidFormat: log.warning('Invalid JID: %s, ignoring it' % item.getAttr('jid')) continue name = item.getAttr('name') sub = item.getAttr('subscription') ask = item.getAttr('ask') groups = [] for group in item.getTags('group'): groups.append(group.getData()) self.items[jid] = {'name': name, 'sub': sub, 'ask': ask, 'groups': groups} if len(self.items) > 1: reply = nbxmpp.Iq(typ='error', attrs={'id': self.stanza.getID()}, to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None) self.conn.connection.send(reply) return if self.conn.connection and self.conn.connected > 1: reply = nbxmpp.Iq(typ='result', attrs={'id': self.stanza.getID()}, to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None) self.conn.connection.send(reply) return True class RosterInfoEvent(nec.NetworkIncomingEvent): name = 'roster-info' base_network_events = [] def init(self): self.avatar_sha = None class MucOwnerReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'muc-owner-received' base_network_events = [] def generate(self): self.get_jid_resource() qp = self.stanza.getQueryPayload() self.form_node = None for q in qp: if q.getNamespace() == nbxmpp.NS_DATA: self.form_node = q self.dataform = dataforms.ExtendForm(node=self.form_node) return True class MucAdminReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'muc-admin-received' base_network_events = [] def generate(self): self.get_jid_resource() items = self.stanza.getTag('query', namespace=nbxmpp.NS_MUC_ADMIN).getTags('item') self.users_dict = {} for item in items: if item.has_attr('jid') and item.has_attr('affiliation'): try: jid = helpers.parse_jid(item.getAttr('jid')) except helpers.InvalidFormat: log.warning('Invalid JID: %s, ignoring it' % \ item.getAttr('jid')) continue affiliation = item.getAttr('affiliation') self.users_dict[jid] = {'affiliation': affiliation} if item.has_attr('nick'): self.users_dict[jid]['nick'] = item.getAttr('nick') if item.has_attr('role'): self.users_dict[jid]['role'] = item.getAttr('role') reason = item.getTagData('reason') if reason: self.users_dict[jid]['reason'] = reason return True class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'iq-error-received' base_network_events = [] def generate(self): self.get_id() self.get_jid_resource(check_fake_jid=True) self.errmsg = self.stanza.getErrorMsg() self.errcode = self.stanza.getErrorCode() return True class StreamReceivedEvent(nec.NetworkIncomingEvent): name = 'stream-received' base_network_events = [] class StreamConflictReceivedEvent(nec.NetworkIncomingEvent): name = 'stream-conflict-received' base_network_events = ['stream-received'] def generate(self): if self.base_event.stanza.getTag('conflict'): self.conn = self.base_event.conn return True class StreamOtherHostReceivedEvent(nec.NetworkIncomingEvent): name = 'stream-other-host-received' base_network_events = ['stream-received'] def generate(self): self.conn = self.base_event.conn self.stanza = self.base_event.stanza other_host = self.stanza.getTag('see-other-host') if other_host and self.conn._current_type in ('ssl', 'tls'): host = other_host.getData() if ':' in host: host_l = host.split(':', 1) h = host_l[0] p = host_l[1] else: h = host p = 5222 if h.startswith('[') and h.endswith(']'): h = h[1:-1] self.redirected = {'host': h, 'port': p} return True class PresenceHelperEvent: def _generate_show(self): self.show = self.stanza.getShow() if self.show not in ('chat', 'away', 'xa', 'dnd'): self.show = '' # We ignore unknown show if not self.ptype and not self.show: self.show = 'online' elif self.ptype == 'unavailable': self.show = 'offline' def _generate_ptype(self): self.ptype = self.stanza.getType() if self.ptype == 'available': self.ptype = None rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed') if self.ptype and not self.ptype in rfc_types: self.ptype = None class PresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent, PresenceHelperEvent): name = 'presence-received' base_network_events = ['raw-pres-received'] def _generate_keyID(self, sig_tag): self.keyID = '' if sig_tag and self.conn.USE_GPG and self.ptype != 'error': # error presences contain our own signature # verify sig_msg = sig_tag.getData() self.keyID = self.conn.gpg.verify(self.status, sig_msg) self.keyID = helpers.prepare_and_validate_gpg_keyID(self.conn.name, self.jid, self.keyID) def _generate_prio(self): self.prio = self.stanza.getPriority() try: self.prio = int(self.prio) except Exception: self.prio = 0 def generate(self): self.conn = self.base_event.conn self.stanza = self.base_event.stanza self.need_add_in_roster = False self.need_redraw = False self.popup = False # Do we want to open chat window ? if not self.conn or self.conn.connected < 2: log.debug('account is no more connected') return self._generate_ptype() try: self.get_jid_resource() except Exception: log.warning('Invalid JID: %s, ignoring it' % self.stanza.getFrom()) return jid_list = app.contacts.get_jid_list(self.conn.name) self.timestamp = None self.get_id() self.is_gc = False # is it a GC presence ? sig_tag = None self.avatar_sha = None # XEP-0172 User Nickname self.user_nick = self.stanza.getTagData('nick') or '' self.contact_nickname = None self.transport_auto_auth = False # XEP-0203 delay_tag = self.stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2) if delay_tag: self._generate_timestamp(self.stanza.timestamp) # XEP-0319 self.idle_time = None idle_tag = self.stanza.getTag('idle', namespace=nbxmpp.NS_IDLE) if idle_tag: time_str = idle_tag.getAttr('since') tim = helpers.datetime_tuple(time_str) self.idle_time = timegm(tim) xtags = self.stanza.getTags('x') for x in xtags: namespace = x.getNamespace() if namespace == nbxmpp.NS_MUC_USER: self.is_gc = True elif namespace == nbxmpp.NS_SIGNED: sig_tag = x elif namespace == nbxmpp.NS_DELAY and not self.timestamp: # XEP-0091 self._generate_timestamp(self.stanza.timestamp) elif namespace == 'http://delx.cjb.net/protocol/roster-subsync': # see http://trac.gajim.org/ticket/326 agent = app.get_server_from_jid(self.jid) if self.conn.connection.getRoster().getItem(agent): # to be sure it's a transport contact self.transport_auto_auth = True if not self.is_gc and self.id_ and self.id_.startswith('gajim_muc_') \ and self.ptype == 'error': # Error presences may not include sent stanza, so we don't detect # it's a muc presence. So detect it by ID h = hmac.new(self.conn.secret_hmac, self.jid.encode('utf-8'), hashlib.md5).hexdigest()[:6] if self.id_.split('_')[-1] == h: self.is_gc = True self.status = self.stanza.getStatus() or '' self._generate_show() self._generate_prio() self._generate_keyID(sig_tag) self.errcode = self.stanza.getErrorCode() self.errmsg = self.stanza.getErrorMsg() if self.is_gc: app.nec.push_incoming_event( GcPresenceReceivedEvent( None, conn=self.conn, stanza=self.stanza, presence_obj=self)) return if self.ptype == 'subscribe': app.nec.push_incoming_event(SubscribePresenceReceivedEvent(None, conn=self.conn, stanza=self.stanza, presence_obj=self)) elif self.ptype == 'subscribed': # BE CAREFUL: no con.updateRosterItem() in a callback app.nec.push_incoming_event(SubscribedPresenceReceivedEvent(None, conn=self.conn, stanza=self.stanza, presence_obj=self)) elif self.ptype == 'unsubscribe': log.debug(_('unsubscribe request from %s') % self.jid) elif self.ptype == 'unsubscribed': app.nec.push_incoming_event(UnsubscribedPresenceReceivedEvent( None, conn=self.conn, stanza=self.stanza, presence_obj=self)) elif self.ptype == 'error': return if not self.ptype or self.ptype == 'unavailable': our_jid = app.get_jid_from_account(self.conn.name) if self.jid == our_jid and self.resource == self.conn.server_resource: # We got our own presence app.nec.push_incoming_event(OurShowEvent(None, conn=self.conn, show=self.show)) elif self.jid in jid_list or self.jid == our_jid: return True class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent): name = 'presence-received' base_network_events = [] def generate(self): self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid) self.resource = 'local' self.prio = 0 self.keyID = None self.idle_time = None self.timestamp = 0 self.contact_nickname = None self.avatar_sha = None self.need_add_in_roster = False self.need_redraw = False if self.show == 'offline': self.ptype = 'unavailable' else: self.ptype = None self.is_gc = False self.user_nick = '' self.transport_auto_auth = False self.errcode = None self.errmsg = '' self.popup = False # Do we want to open chat window ? return True class GcPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'gc-presence-received' base_network_events = [] def generate(self): self.ptype = self.presence_obj.ptype self.fjid = self.presence_obj.fjid self.jid = self.presence_obj.jid self.room_jid = self.presence_obj.jid self.nick = self.presence_obj.resource self.show = self.presence_obj.show self.status = self.presence_obj.status self.avatar_sha = self.presence_obj.avatar_sha self.errcode = self.presence_obj.errcode self.errmsg = self.presence_obj.errmsg self.errcon = self.stanza.getError() self.get_gc_control() self.gc_contact = app.contacts.get_gc_contact(self.conn.name, self.room_jid, self.nick) if self.ptype == 'error': return True if self.ptype and self.ptype != 'unavailable': return if app.config.get('log_contact_status_changes') and \ app.config.should_log(self.conn.name, self.room_jid): if self.gc_contact: jid = self.gc_contact.jid else: jid = self.stanza.getJid() st = self.status if jid: # we know real jid, save it in db st += ' (%s)' % jid show = app.logger.convert_show_values_to_db_api_values(self.show) if show is not None: fjid = nbxmpp.JID(self.fjid) app.logger.insert_into_logs(self.conn.name, fjid.getStripped(), time_time(), KindConstant.GCSTATUS, contact_name=fjid.getResource(), message=st, show=show) # NOTE: if it's a gc presence, don't ask vcard here. # We may ask it to real jid in gui part. self.status_code = [] ns_muc_user_x = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) if ns_muc_user_x: destroy = ns_muc_user_x.getTag('destroy') else: destroy = None if ns_muc_user_x and destroy: # Room has been destroyed. see # http://www.xmpp.org/extensions/xep-0045.html#destroyroom self.reason = _('Room has been destroyed') r = destroy.getTagData('reason') if r: self.reason += ' (%s)' % r if destroy.getAttr('jid'): try: jid = helpers.parse_jid(destroy.getAttr('jid')) self.reason += '\n' + \ _('You can join this room instead: %s') % jid except helpers.InvalidFormat: pass self.status_code = ['destroyed'] else: self.reason = self.stanza.getReason() conditions = self.stanza.getStatusConditions() if conditions: self.status_code = [] for condition in conditions: if condition in CONDITION_TO_CODE: self.status_code.append(CONDITION_TO_CODE[condition]) else: self.status_code = self.stanza.getStatusCode() self.role = self.stanza.getRole() self.affiliation = self.stanza.getAffiliation() self.real_jid = self.stanza.getJid() self.actor = self.stanza.getActor() self.new_nick = self.stanza.getNewNick() return True class SubscribePresenceReceivedEvent(nec.NetworkIncomingEvent): name = 'subscribe-presence-received' base_network_events = [] def generate(self): self.jid = self.presence_obj.jid self.fjid = self.presence_obj.fjid self.status = self.presence_obj.status self.transport_auto_auth = self.presence_obj.transport_auto_auth self.user_nick = self.presence_obj.user_nick return True class SubscribedPresenceReceivedEvent(nec.NetworkIncomingEvent): name = 'subscribed-presence-received' base_network_events = [] def generate(self): self.jid = self.presence_obj.jid self.resource = self.presence_obj.resource return True class UnsubscribedPresenceReceivedEvent(nec.NetworkIncomingEvent): name = 'unsubscribed-presence-received' base_network_events = [] def generate(self): self.jid = self.presence_obj.jid return True class OurShowEvent(nec.NetworkIncomingEvent): name = 'our-show' base_network_events = [] class BeforeChangeShowEvent(nec.NetworkIncomingEvent): name = 'before-change-show' base_network_events = [] class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'mam-message-received' base_network_events = ['raw-mam-message-received'] def __init__(self, name, base_event): ''' Pre-Generated attributes on self: :conn: Connection instance :stanza: Complete stanza Node :forwarded: Forwarded Node :result: Result Node ''' self._set_base_event_vars_as_attributes(base_event) self.additional_data = {} self.encrypted = False self.groupchat = False self.nick = None self.self_message = None self.muc_pm = None def generate(self): account = self.conn.name archive_jid = self.stanza.getFrom() own_jid = self.conn.get_own_jid() if archive_jid and not archive_jid.bareMatch(own_jid): # MAM Message not from our Archive return False self.msg_ = self.forwarded.getTag('message', protocol=True) if self.msg_.getType() == 'groupchat': return False # use stanza-id as unique-id self.unique_id, origin_id = self.get_unique_id() self.message_id = self.msg_.getID() # Check for duplicates if app.logger.find_stanza_id(account, own_jid.getStripped(), self.unique_id, origin_id): return self.msgtxt = self.msg_.getTagData('body') frm = self.msg_.getFrom() # Some servers dont set the 'to' attribute when # we send a message to ourself to = self.msg_.getTo() if to is None: to = own_jid if frm.bareMatch(own_jid): self.with_ = to self.kind = KindConstant.CHAT_MSG_SENT else: self.with_ = frm self.kind = KindConstant.CHAT_MSG_RECV delay = self.forwarded.getTagAttr( 'delay', 'stamp', namespace=nbxmpp.NS_DELAY2) if delay is None: log.error('Received MAM message without timestamp') log.error(self.stanza) return self.timestamp = helpers.parse_datetime( delay, check_utc=True, epoch=True) if self.timestamp is None: log.error('Received MAM message with invalid timestamp: %s', delay) log.error(self.stanza) return # Save timestamp added by the user user_delay = self.msg_.getTagAttr( 'delay', 'stamp', namespace=nbxmpp.NS_DELAY2) if user_delay is not None: self.user_timestamp = helpers.parse_datetime( user_delay, check_utc=True, epoch=True) if self.user_timestamp is None: log.warning('Received MAM message with ' 'invalid user timestamp: %s', user_delay) log.warning(self.stanza) log.debug('Received mam-message: unique id: %s', self.unique_id) return True def get_unique_id(self): stanza_id = self.get_stanza_id(self.result, query=True) if self._is_self_message(self.msg_) or self._is_muc_pm(self.msg_): origin_id = self.msg_.getOriginID() return stanza_id, origin_id if self.conn.get_own_jid().bareMatch(self.msg_.getFrom()): # message we sent origin_id = self.msg_.getOriginID() return stanza_id, origin_id # A message we received return stanza_id, None class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'mam-gc-message-received' base_network_events = ['raw-mam-message-received'] def __init__(self, name, base_event): ''' Pre-Generated attributes on self: :conn: Connection instance :stanza: Complete stanza Node :forwarded: Forwarded Node :result: Result Node :muc_pm: True, if this is a MUC PM propagated to MamDecryptedMessageReceivedEvent ''' self._set_base_event_vars_as_attributes(base_event) self.additional_data = {} self.encrypted = False self.groupchat = True self.kind = KindConstant.GC_MSG def generate(self): account = self.conn.name self.msg_ = self.forwarded.getTag('message', protocol=True) if self.msg_.getType() != 'groupchat': return False try: self.room_jid = self.stanza.getFrom().getStripped() except AttributeError: log.warning('Received GC MAM message ' 'without from attribute\n%s', self.stanza) return False self.unique_id = self.get_stanza_id(self.result, query=True) self.message_id = self.msg_.getID() # Check for duplicates if app.logger.find_stanza_id(account, self.room_jid, self.unique_id, groupchat=True): return self.msgtxt = self.msg_.getTagData('body') self.with_ = self.msg_.getFrom().getStripped() self.nick = self.msg_.getFrom().getResource() # Get the real jid if we have it self.real_jid = None muc_user = self.msg_.getTag('x', namespace=nbxmpp.NS_MUC_USER) if muc_user is not None: self.real_jid = muc_user.getTagAttr('item', 'jid') delay = self.forwarded.getTagAttr( 'delay', 'stamp', namespace=nbxmpp.NS_DELAY2) if delay is None: log.error('Received MAM message without timestamp') log.error(self.stanza) return self.timestamp = helpers.parse_datetime( delay, check_utc=True, epoch=True) if self.timestamp is None: log.error('Received MAM message with invalid timestamp: %s', delay) log.error(self.stanza) return # Save timestamp added by the user user_delay = self.msg_.getTagAttr( 'delay', 'stamp', namespace=nbxmpp.NS_DELAY2) if user_delay is not None: self.user_timestamp = helpers.parse_datetime( user_delay, check_utc=True, epoch=True) if self.user_timestamp is None: log.warning('Received MAM message with ' 'invalid user timestamp: %s', user_delay) log.warning(self.stanza) log.debug('Received mam-gc-message: unique id: %s', self.unique_id) return True class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'mam-decrypted-message-received' base_network_events = [] def generate(self): self.correct_id = None if not self.msgtxt: # For example Chatstates, Receipts, Chatmarkers log.debug('Received MAM message without text') return replace = self.msg_.getTag('replace', namespace=nbxmpp.NS_CORRECT) if replace is not None: self.correct_id = replace.getAttr('id') self.get_oob_data(self.msg_) if self.groupchat: return True if not self.muc_pm: # muc_pm = False, means only there was no muc#user namespace # This could still be a muc pm, we check the database if we # know this jid. If not we disco it. self.muc_pm = app.logger.jid_is_room_jid(self.with_.getStripped()) if self.muc_pm is None: # Check if this event is triggered after a disco, so we dont # run into an endless loop if hasattr(self, 'disco'): log.error('JID not known even after sucessful disco') log.error(self.with_.getStripped()) return # we don't know this JID, we need to disco it. server = self.with_.getDomain() if server not in self.conn.mam_awaiting_disco_result: self.conn.mam_awaiting_disco_result[server] = [self] self.conn.discoverInfo(server) else: self.conn.mam_awaiting_disco_result[server].append(self) return if self.muc_pm: self.with_ = str(self.with_) else: self.with_ = self.with_.getStripped() return True class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'message-received' base_network_events = ['raw-message-received'] def init(self): self.additional_data = {} def generate(self): self.conn = self.base_event.conn self.stanza = self.base_event.stanza self.get_id() self.forwarded = False self.sent = False self.encrypted = False self.self_message = None self.muc_pm = None account = self.conn.name if self.stanza.getFrom() == self.conn.get_own_jid(warn=True): # Drop messages sent from our own full jid # It can happen that when we sent message to our own bare jid # that the server routes that message back to us log.info('Received message from self: %s, message is dropped', self.stanza.getFrom()) return try: self.get_jid_resource() except helpers.InvalidFormat: log.warning('Invalid JID: %s, ignoring it', self.stanza.getFrom()) return # Check for duplicates self.unique_id = self.get_unique_id() # Check groupchat messages for duplicates, # We do this because of MUC History messages type_ = self.stanza.getType() if type_ == 'groupchat' or self.self_message or self.muc_pm: if type_ == 'groupchat': archive_jid = self.stanza.getFrom().getStripped() else: archive_jid = self.conn.get_own_jid().getStripped() if app.logger.find_stanza_id(account, archive_jid, self.unique_id, groupchat=type_ == 'groupchat'): return address_tag = self.stanza.getTag('addresses', namespace=nbxmpp.NS_ADDRESS) # Be sure it comes from one of our resource, else ignore address element if address_tag and self.jid == app.get_jid_from_account(account): address = address_tag.getTag('address', attrs={'type': 'ofrom'}) if address: try: self.fjid = helpers.parse_jid(address.getAttr('jid')) except helpers.InvalidFormat: log.warning('Invalid JID: %s, ignoring it', address.getAttr('jid')) return self.jid = app.get_jid_without_resource(self.fjid) carbon_marker = self.stanza.getTag('sent', namespace=nbxmpp.NS_CARBONS) if not carbon_marker: carbon_marker = self.stanza.getTag('received', namespace=nbxmpp.NS_CARBONS) # Be sure it comes from one of our resource, else ignore forward element if carbon_marker and self.jid == app.get_jid_from_account(account): forward_tag = carbon_marker.getTag('forwarded', namespace=nbxmpp.NS_FORWARD) if forward_tag: msg = forward_tag.getTag('message') self.stanza = nbxmpp.Message(node=msg) self.get_id() if carbon_marker.getName() == 'sent': to = self.stanza.getTo() frm = self.stanza.getFrom() if not frm: frm = app.get_jid_from_account(account) self.stanza.setTo(frm) if not to: to = app.get_jid_from_account(account) self.stanza.setFrom(to) self.sent = True elif carbon_marker.getName() == 'received': full_frm = str(self.stanza.getFrom()) frm = app.get_jid_without_resource(full_frm) if frm == app.get_jid_from_account(account): # Drop 'received' Carbons from ourself, we already # got the message with the 'sent' Carbon or via the # message itself log.info( 'Drop "received"-Carbon from ourself: %s' % full_frm) return try: self.get_jid_resource() except helpers.InvalidFormat: log.warning('Invalid JID: %s, ignoring it', self.stanza.getFrom()) return self.forwarded = True result = self.stanza.getTag('result', protocol=True) if result and result.getNamespace() in (nbxmpp.NS_MAM_1, nbxmpp.NS_MAM_2): if result.getAttr('queryid') not in self.conn.mam_query_ids: log.warning('Invalid MAM Message: unknown query id') log.debug(self.stanza) return forwarded = result.getTag('forwarded', namespace=nbxmpp.NS_FORWARD, protocol=True) if not forwarded: log.warning('Invalid MAM Message: no forwarded child') return app.nec.push_incoming_event( NetworkEvent('raw-mam-message-received', conn=self.conn, stanza=self.stanza, forwarded=forwarded, result=result)) return # Mediated invitation? muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) if muc_user: if muc_user.getTag('decline'): app.nec.push_incoming_event( GcDeclineReceivedEvent( None, conn=self.conn, room_jid=self.fjid, stanza=muc_user)) return if muc_user.getTag('invite'): app.nec.push_incoming_event( GcInvitationReceivedEvent( None, conn=self.conn, jid_from=self.fjid, mediated=True, stanza=muc_user)) return else: # Direct invitation? direct = self.stanza.getTag( 'x', namespace=nbxmpp.NS_CONFERENCE) if direct: app.nec.push_incoming_event( GcInvitationReceivedEvent( None, conn=self.conn, jid_from=self.fjid, mediated=False, stanza=direct)) return self.thread_id = self.stanza.getThread() self.mtype = self.stanza.getType() if not self.mtype or self.mtype not in ('chat', 'groupchat', 'error'): self.mtype = 'normal' self.msgtxt = self.stanza.getBody() self.get_gc_control() if self.gc_control and self.jid == self.fjid: if self.mtype == 'error': self.msgtxt = _('error while sending %(message)s ( %(error)s )'\ ) % {'message': self.msgtxt, 'error': self.stanza.getErrorMsg()} if self.stanza.getTag('html'): self.stanza.delChild('html') # message from a gc without a resource self.mtype = 'groupchat' self.session = None if self.mtype != 'groupchat': if app.interface.is_pm_contact(self.fjid, account) and \ self.mtype == 'error': self.session = self.conn.find_session(self.fjid, self.thread_id) if not self.session: self.session = self.conn.get_latest_session(self.fjid) if not self.session: self.session = self.conn.make_new_session(self.fjid, self.thread_id, type_='pm') else: self.session = self.conn.get_or_create_session(self.fjid, self.thread_id) if self.thread_id and not self.session.received_thread_id: self.session.received_thread_id = True self.session.last_receive = time_time() self._generate_timestamp(self.stanza.timestamp) return True def get_unique_id(self): ''' Messages to self: Messages to self are stored multiple times in MAM so we cant use stanza-id to deduplicate. We use origin-id instead. Its not perfect but there is no better way for now. We drop "received"-Carbons of Message to self, so we dont have to parse origin-id in that case. MUC PMs: MUC PMs are also stored multiple times, we also depend on origin-id for now. ''' if self.stanza.getType() == 'groupchat': # TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced return self.get_stanza_id(self.stanza) elif self.stanza.getType() != 'chat': return # Messages we receive live if self.conn.archiving_namespace != nbxmpp.NS_MAM_2: # Only mam:2 ensures valid stanza-id return # Sent Carbon sent_carbon = self.stanza.getTag('sent', namespace=nbxmpp.NS_CARBONS, protocol=True) if sent_carbon is not None: message = self.get_forwarded_message(sent_carbon) if self._is_self_message(message) or self._is_muc_pm(message): return message.getOriginID() return self.get_stanza_id(message) # Received Carbon received_carbon = self.stanza.getTag('received', namespace=nbxmpp.NS_CARBONS, protocol=True) if received_carbon is not None: message = self.get_forwarded_message(received_carbon) if self._is_muc_pm(message): return message.getOriginID() return self.get_stanza_id(message) # Normal Message if self._is_self_message(self.stanza) or self._is_muc_pm(self.stanza): return self.stanza.getOriginID() return self.get_stanza_id(self.stanza) class ZeroconfMessageReceivedEvent(MessageReceivedEvent): name = 'message-received' base_network_events = [] def get_jid_resource(self): self.fjid = str(self.stanza.getFrom()) if self.fjid is None: for key in self.conn.connection.zeroconf.contacts: if self.ip == self.conn.connection.zeroconf.contacts[key][ Constant.ADDRESS]: self.fjid = key break self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid) def generate(self): self.base_event = nec.NetworkIncomingEvent(None, conn=self.conn, stanza=self.stanza) return super(ZeroconfMessageReceivedEvent, self).generate() class GcInvitationReceivedEvent(nec.NetworkIncomingEvent): name = 'gc-invitation-received' base_network_events = [] def generate(self): account = self.conn.name if not self.mediated: # direct invitation try: self.room_jid = helpers.parse_jid(self.stanza.getAttr('jid')) except helpers.InvalidFormat: log.warning('Invalid JID: %s, ignoring it', self.stanza.getAttr('jid')) return self.reason = self.stanza.getAttr('reason') self.password = self.stanza.getAttr('password') self.is_continued = False self.is_continued = self.stanza.getAttr('continue') == 'true' else: self.invite = self.stanza.getTag('invite') self.room_jid = self.jid_from try: self.jid_from = helpers.parse_jid(self.invite.getAttr('from')) except helpers.InvalidFormat: log.warning('Invalid JID: %s, ignoring it', self.invite.getAttr('from')) return self.reason = self.invite.getTagData('reason') self.password = self.stanza.getTagData('password') self.is_continued = self.stanza.getTag('continue') is not None if self.room_jid in app.gc_connected[account] and \ app.gc_connected[account][self.room_jid]: # We are already in groupchat. Ignore invitation return jid = app.get_jid_without_resource(self.jid_from) ignore = app.config.get_per( 'accounts', account, 'ignore_unknown_contacts') if ignore and not app.contacts.get_contacts(account, jid): return return True class GcDeclineReceivedEvent(nec.NetworkIncomingEvent): name = 'gc-decline-received' base_network_events = [] def generate(self): account = self.conn.name decline = self.stanza.getTag('decline') try: self.jid_from = helpers.parse_jid(decline.getAttr('from')) except helpers.InvalidFormat: log.warning('Invalid JID: %s, ignoring it', decline.getAttr('from')) return jid = app.get_jid_without_resource(self.jid_from) ignore = app.config.get_per( 'accounts', account, 'ignore_unknown_contacts') if ignore and not app.contacts.get_contacts(account, jid): return self.reason = decline.getTagData('reason') return True class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'decrypted-message-received' base_network_events = [] def generate(self): self.stanza = self.msg_obj.stanza self.additional_data = self.msg_obj.additional_data self.id_ = self.msg_obj.id_ self.unique_id = self.msg_obj.unique_id self.jid = self.msg_obj.jid self.fjid = self.msg_obj.fjid self.resource = self.msg_obj.resource self.mtype = self.msg_obj.mtype self.thread_id = self.msg_obj.thread_id self.msgtxt = self.msg_obj.msgtxt self.gc_control = self.msg_obj.gc_control self.session = self.msg_obj.session self.timestamp = self.msg_obj.timestamp self.encrypted = self.msg_obj.encrypted self.forwarded = self.msg_obj.forwarded self.sent = self.msg_obj.sent self.conn = self.msg_obj.conn self.muc_pm = self.msg_obj.muc_pm self.popup = False self.msg_log_id = None # id in log database self.attention = False # XEP-0224 self.correct_id = None # XEP-0308 self.msghash = None self.receipt_request_tag = self.stanza.getTag('request', namespace=nbxmpp.NS_RECEIPTS) self.receipt_received_tag = self.stanza.getTag('received', namespace=nbxmpp.NS_RECEIPTS) self.subject = self.stanza.getSubject() self.displaymarking = None self.seclabel = self.stanza.getTag('securitylabel', namespace=nbxmpp.NS_SECLABEL) if self.seclabel: self.displaymarking = self.seclabel.getTag('displaymarking') if self.stanza.getTag('attention', namespace=nbxmpp.NS_ATTENTION): delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not\ None if not delayed: self.attention = True self.form_node = self.stanza.getTag('x', namespace=nbxmpp.NS_DATA) if app.config.get('ignore_incoming_xhtml'): self.xhtml = None else: self.xhtml = self.stanza.getXHTML() # XEP-0172 User Nickname self.user_nick = self.stanza.getTagData('nick') or '' self.get_chatstate() self.get_oob_data(self.stanza) replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT) if replace: self.correct_id = replace.getAttr('id') return True class ChatstateReceivedEvent(nec.NetworkIncomingEvent): name = 'chatstate-received' base_network_events = [] def generate(self): self.stanza = self.msg_obj.stanza self.jid = self.msg_obj.jid self.fjid = self.msg_obj.fjid self.resource = self.msg_obj.resource self.chatstate = self.msg_obj.chatstate return True class GcMessageReceivedEvent(nec.NetworkIncomingEvent): name = 'gc-message-received' base_network_events = [] def generate(self): self.stanza = self.msg_obj.stanza if not hasattr(self.msg_obj, 'additional_data'): self.additional_data = {} else: self.additional_data = self.msg_obj.additional_data self.id_ = self.msg_obj.stanza.getID() self.unique_id = self.msg_obj.unique_id self.fjid = self.msg_obj.fjid self.msgtxt = self.msg_obj.msgtxt self.jid = self.msg_obj.jid self.room_jid = self.msg_obj.jid self.nickname = self.msg_obj.resource self.timestamp = self.msg_obj.timestamp self.xhtml_msgtxt = self.stanza.getXHTML() self.encrypted = self.msg_obj.encrypted self.correct_id = None # XEP-0308 if app.config.get('ignore_incoming_xhtml'): self.xhtml_msgtxt = None if self.msg_obj.resource: # message from someone self.nick = self.msg_obj.resource else: # message from server self.nick = '' self.has_timestamp = bool(self.stanza.timestamp) self.subject = self.stanza.getSubject() if self.subject is not None: app.nec.push_incoming_event(GcSubjectReceivedEvent(None, conn=self.conn, msg_event=self)) return conditions = self.stanza.getStatusConditions() if conditions: self.status_code = [] for condition in conditions: if condition in CONDITION_TO_CODE: self.status_code.append(CONDITION_TO_CODE[condition]) else: self.status_code = self.stanza.getStatusCode() if not self.stanza.getTag('body'): # no # It could be a config change. See # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify if self.stanza.getTag('x'): if self.status_code != []: app.nec.push_incoming_event(GcConfigChangedReceivedEvent( None, conn=self.conn, msg_event=self)) if self.msg_obj.form_node: # It could be a voice request. See # http://www.xmpp.org/extensions/xep-0045.html#voiceapprove from gajim.dialogs import SingleMessageWindow SingleMessageWindow( self.conn.name, self.fjid, action='receive', from_whom=self.fjid, subject='', message='', resource='', session=None, form_node=self.msg_obj.form_node) return self.displaymarking = None seclabel = self.stanza.getTag('securitylabel') if seclabel and seclabel.getNamespace() == nbxmpp.NS_SECLABEL: # Ignore message from room in which we are not self.displaymarking = seclabel.getTag('displaymarking') self.captcha_form = None captcha_tag = self.stanza.getTag('captcha', namespace=nbxmpp.NS_CAPTCHA) if captcha_tag: self.captcha_form = captcha_tag.getTag('x', namespace=nbxmpp.NS_DATA) for field in self.captcha_form.getTags('field'): for media in field.getTags('media'): for uri in media.getTags('uri'): uri_data = uri.getData() if uri_data.startswith('cid:'): uri_data = uri_data[4:] found = False for data in self.stanza.getTags('data', namespace=nbxmpp.NS_BOB): if data.getAttr('cid') == uri_data: uri.setData(data.getData()) found = True if not found: self.conn.get_bob_data(uri_data, self.fjid, self.conn._dispatch_gc_msg_with_captcha, [self.stanza, self.msg_obj], 0) return replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT) if replace: self.correct_id = replace.getAttr('id') return True class GcSubjectReceivedEvent(nec.NetworkIncomingEvent): name = 'gc-subject-received' base_network_events = [] def generate(self): self.conn = self.msg_event.conn self.stanza = self.msg_event.stanza self.room_jid = self.msg_event.room_jid self.nickname = self.msg_event.nickname self.fjid = self.msg_event.fjid self.subject = self.msg_event.subject self.msgtxt = self.msg_event.msgtxt self.has_timestamp = self.msg_event.has_timestamp return True class GcConfigChangedReceivedEvent(nec.NetworkIncomingEvent): name = 'gc-config-changed-received' base_network_events = [] def generate(self): self.conn = self.msg_event.conn self.stanza = self.msg_event.stanza self.room_jid = self.msg_event.room_jid self.status_code = self.msg_event.status_code return True class MessageSentEvent(nec.NetworkIncomingEvent): name = 'message-sent' base_network_events = [] def generate(self): if not self.automatic_message: self.conn.sent_message_ids.append(self.stanza_id) # only record the last 20000 message ids (should be about 1MB [36 byte per uuid] # and about 24 hours if you send out a message every 5 seconds) self.conn.sent_message_ids = self.conn.sent_message_ids[-20000:] return True class MessageNotSentEvent(nec.NetworkIncomingEvent): name = 'message-not-sent' base_network_events = [] class MessageErrorEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'message-error' base_network_events = [] def init(self): self.zeroconf = False def generate(self): if self.zeroconf: return True self.get_id() #only alert for errors of explicitly sent messages (see https://trac.gajim.org/ticket/8222) if self.id_ in self.conn.sent_message_ids: self.conn.sent_message_ids.remove(self.id_) return True return False class AnonymousAuthEvent(nec.NetworkIncomingEvent): name = 'anonymous-auth' base_network_events = [] class JingleRequestReceivedEvent(nec.NetworkIncomingEvent): name = 'jingle-request-received' base_network_events = [] def generate(self): self.fjid = self.jingle_session.peerjid self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid) self.sid = self.jingle_session.sid return True class JingleConnectedReceivedEvent(nec.NetworkIncomingEvent): name = 'jingle-connected-received' base_network_events = [] def generate(self): self.fjid = self.jingle_session.peerjid self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid) self.sid = self.jingle_session.sid return True class JingleDisconnectedReceivedEvent(nec.NetworkIncomingEvent): name = 'jingle-disconnected-received' base_network_events = [] def generate(self): self.fjid = self.jingle_session.peerjid self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid) self.sid = self.jingle_session.sid return True class JingleTransferCancelledEvent(nec.NetworkIncomingEvent): name = 'jingleFT-cancelled-received' base_network_events = [] def generate(self): self.fjid = self.jingle_session.peerjid self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid) self.sid = self.jingle_session.sid return True class JingleErrorReceivedEvent(nec.NetworkIncomingEvent): name = 'jingle-error-received' base_network_events = [] def generate(self): self.fjid = self.jingle_session.peerjid self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid) self.sid = self.jingle_session.sid return True class ArchivingReceivedEvent(nec.NetworkIncomingEvent): name = 'archiving-received' base_network_events = [] def generate(self): self.type_ = self.stanza.getType() if self.type_ not in ('result', 'set', 'error'): return return True class ArchivingErrorReceivedEvent(nec.NetworkIncomingEvent): name = 'archiving-error-received' base_network_events = ['archiving-received'] def generate(self): self.conn = self.base_event.conn self.stanza = self.base_event.stanza self.type_ = self.base_event.type_ if self.type_ == 'error': self.error_msg = self.stanza.getErrorMsg() return True class ArchivingCountReceived(nec.NetworkIncomingEvent): name = 'archiving-count-received' base_network_events = [] def generate(self): return True class ArchivingIntervalFinished(nec.NetworkIncomingEvent): name = 'archiving-interval-finished' base_network_events = [] def generate(self): return True class ArchivingQueryID(nec.NetworkIncomingEvent): name = 'archiving-query-id' base_network_events = [] def generate(self): return True class Archiving313PreferencesChangedReceivedEvent(nec.NetworkIncomingEvent): name = 'archiving-313-preferences-changed-received' base_network_events = ['archiving-received'] def generate(self): self.conn = self.base_event.conn self.stanza = self.base_event.stanza self.type_ = self.base_event.type_ self.items = [] self.default = None self.id = self.stanza.getID() self.answer = None prefs = self.stanza.getTag('prefs') if self.type_ != 'result' or not prefs: return self.default = prefs.getAttr('default') for item in prefs.getTag('always').getTags('jid'): self.items.append((item.getData(), 'Always')) for item in prefs.getTag('never').getTags('jid'): self.items.append((item.getData(), 'Never')) return True class AccountCreatedEvent(nec.NetworkIncomingEvent): name = 'account-created' base_network_events = [] class AccountNotCreatedEvent(nec.NetworkIncomingEvent): name = 'account-not-created' base_network_events = [] class NewAccountConnectedEvent(nec.NetworkIncomingEvent): name = 'new-account-connected' base_network_events = [] def generate(self): try: self.errnum = self.conn.connection.Connection.ssl_errnum except AttributeError: self.errnum = 0 # we don't have an errnum self.ssl_msg = '' if self.errnum > 0: self.ssl_msg = SSLError.get(self.errnum, _('Unknown SSL error: %d') % self.errnum) self.ssl_cert = '' self.ssl_fingerprint_sha1 = '' self.ssl_fingerprint_sha256 = '' if self.conn.connection.Connection.ssl_certificate: cert = self.conn.connection.Connection.ssl_certificate self.ssl_cert = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, cert).decode('utf-8') self.ssl_fingerprint_sha1 = cert.digest('sha1').decode('utf-8') self.ssl_fingerprint_sha256 = cert.digest('sha256').decode('utf-8') return True class NewAccountNotConnectedEvent(nec.NetworkIncomingEvent): name = 'new-account-not-connected' base_network_events = [] class ConnectionTypeEvent(nec.NetworkIncomingEvent): name = 'connection-type' base_network_events = [] class StanzaReceivedEvent(nec.NetworkIncomingEvent): name = 'stanza-received' base_network_events = [] def init(self): self.additional_data = {} def generate(self): return True class StanzaSentEvent(nec.NetworkIncomingEvent): name = 'stanza-sent' base_network_events = [] def init(self): self.additional_data = {} class AgentRemovedEvent(nec.NetworkIncomingEvent): name = 'agent-removed' base_network_events = [] def generate(self): self.jid_list = [] for jid in app.contacts.get_jid_list(self.conn.name): if jid.endswith('@' + self.agent): self.jid_list.append(jid) return True class BadGPGPassphraseEvent(nec.NetworkIncomingEvent): name = 'bad-gpg-passphrase' base_network_events = [] def generate(self): self.account = self.conn.name self.use_gpg_agent = app.config.get('use_gpg_agent') self.keyID = app.config.get_per('accounts', self.conn.name, 'keyid') return True class ConnectionLostEvent(nec.NetworkIncomingEvent): name = 'connection-lost' base_network_events = [] def generate(self): app.nec.push_incoming_event(OurShowEvent(None, conn=self.conn, show='offline')) return True class CapsPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent, PresenceHelperEvent): name = 'caps-presence-received' base_network_events = ['raw-pres-received'] def _extract_caps_from_presence(self): caps_tag = self.stanza.getTag('c', namespace=nbxmpp.NS_CAPS) if caps_tag: self.hash_method = caps_tag['hash'] self.node = caps_tag['node'] self.caps_hash = caps_tag['ver'] else: self.hash_method = self.node = self.caps_hash = None def generate(self): self.conn = self.base_event.conn self.stanza = self.base_event.stanza try: self.get_jid_resource() except Exception: return self._generate_ptype() self._generate_show() self._extract_caps_from_presence() return True class CapsDiscoReceivedEvent(nec.NetworkIncomingEvent): name = 'caps-disco-received' base_network_events = [] class CapsReceivedEvent(nec.NetworkIncomingEvent): name = 'caps-received' base_network_events = ['caps-presence-received', 'caps-disco-received'] def generate(self): self.conn = self.base_event.conn self.fjid = self.base_event.fjid self.jid = self.base_event.jid self.resource = self.base_event.resource self.client_caps = self.base_event.client_caps return True class GPGTrustKeyEvent(nec.NetworkIncomingEvent): name = 'gpg-trust-key' base_network_events = [] class GPGPasswordRequiredEvent(nec.NetworkIncomingEvent): name = 'gpg-password-required' base_network_events = [] def generate(self): self.keyid = app.config.get_per('accounts', self.conn.name, 'keyid') return True class PEPReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'pep-received' base_network_events = [] def generate(self): if not self.stanza.getTag('event'): return if self.stanza.getTag('error'): log.debug('PEPReceivedEvent received error stanza. Ignoring') return try: self.get_jid_resource() except Exception: return self.event_tag = self.stanza.getTag('event') for pep_class in SUPPORTED_PERSONAL_USER_EVENTS: pep = pep_class.get_tag_as_PEP(self.fjid, self.conn.name, self.event_tag) if pep: self.pep_type = pep.type_ return True class PlainConnectionEvent(nec.NetworkIncomingEvent): name = 'plain-connection' base_network_events = [] class InsecurePasswordEvent(nec.NetworkIncomingEvent): name = 'insecure-password' base_network_events = [] class InsecureSSLConnectionEvent(nec.NetworkIncomingEvent): name = 'insecure-ssl-connection' base_network_events = [] class SSLErrorEvent(nec.NetworkIncomingEvent): name = 'ssl-error' base_network_events = [] class UniqueRoomIdSupportedEvent(nec.NetworkIncomingEvent): name = 'unique-room-id-supported' base_network_events = [] class UniqueRoomIdNotSupportedEvent(nec.NetworkIncomingEvent): name = 'unique-room-id-not-supported' base_network_events = [] class PrivacyListsReceivedEvent(nec.NetworkIncomingEvent): name = 'privacy-lists-received' base_network_events = [] class PrivacyListReceivedEvent(nec.NetworkIncomingEvent): name = 'privacy-list-received' base_network_events = [] class PrivacyListRemovedEvent(nec.NetworkIncomingEvent): name = 'privacy-list-removed' base_network_events = [] class PrivacyListActiveDefaultEvent(nec.NetworkIncomingEvent): name = 'privacy-list-active-default' base_network_events = [] class NonAnonymousServerErrorEvent(nec.NetworkIncomingEvent): name = 'non-anonymous-server-error' base_network_events = [] class UpdateGCAvatarEvent(nec.NetworkIncomingEvent): name = 'update-gc-avatar' base_network_events = [] def generate(self): return True class UpdateRosterAvatarEvent(nec.NetworkIncomingEvent): name = 'update-roster-avatar' base_network_events = [] def generate(self): return True class UpdateRoomAvatarEvent(nec.NetworkIncomingEvent): name = 'update-room-avatar' base_network_events = [] def generate(self): return True class PEPConfigReceivedEvent(nec.NetworkIncomingEvent): name = 'pep-config-received' base_network_events = [] class MetacontactsReceivedEvent(nec.NetworkIncomingEvent): name = 'metacontacts-received' base_network_events = [] def generate(self): # Metacontact tags # http://www.xmpp.org/extensions/xep-0209.html self.meta_list = {} query = self.stanza.getTag('query') storage = query.getTag('storage') metas = storage.getTags('meta') for meta in metas: try: jid = helpers.parse_jid(meta.getAttr('jid')) except helpers.InvalidFormat: continue tag = meta.getAttr('tag') data = {'jid': jid} order = meta.getAttr('order') try: order = int(order) except Exception: order = 0 if order is not None: data['order'] = order if tag in self.meta_list: self.meta_list[tag].append(data) else: self.meta_list[tag] = [data] return True class ZeroconfNameConflictEvent(nec.NetworkIncomingEvent): name = 'zeroconf-name-conflict' base_network_events = [] class PasswordRequiredEvent(nec.NetworkIncomingEvent): name = 'password-required' base_network_events = [] class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent): name = 'oauth2-credentials-required' base_network_events = [] class SignedInEvent(nec.NetworkIncomingEvent): name = 'signed-in' base_network_events = [] class RegisterAgentInfoReceivedEvent(nec.NetworkIncomingEvent): name = 'register-agent-info-received' base_network_events = [] class AgentItemsReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'agent-items-received' base_network_events = [] def generate(self): q = self.stanza.getTag('query') self.node = q.getAttr('node') if not self.node: self.node = '' qp = self.stanza.getQueryPayload() self.items = [] if not qp: qp = [] for i in qp: # CDATA payload is not processed, only nodes if not isinstance(i, nbxmpp.simplexml.Node): continue attr = {} for key in i.getAttrs(): attr[key] = i.getAttrs()[key] if 'jid' not in attr: continue try: attr['jid'] = helpers.parse_jid(attr['jid']) except helpers.InvalidFormat: # jid is not conform continue self.items.append(attr) self.get_jid_resource() hostname = app.config.get_per('accounts', self.conn.name, 'hostname') self.get_id() if self.id_ in self.conn.disco_items_ids: self.conn.disco_items_ids.remove(self.id_) if self.fjid == hostname and self.id_[:6] == 'Gajim_': for item in self.items: self.conn.discoverInfo(item['jid'], id_prefix='Gajim_') else: return True class AgentItemsErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'agent-items-error-received' base_network_events = [] def generate(self): self.get_jid_resource() self.get_id() if self.id_ in self.conn.disco_items_ids: self.conn.disco_items_ids.remove(self.id_) return True class AgentInfoReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'agent-info-received' base_network_events = [] def generate(self): self.get_id() if self.id_ in self.conn.disco_info_ids: self.conn.disco_info_ids.remove(self.id_) if self.id_ is None: log.warning('Invalid IQ received without an ID. ' 'Ignoring it: %s', self.stanza) return # According to XEP-0030: # For identity: category, type is mandatory, name is optional. # For feature: var is mandatory self.identities, self.features, self.data, self.node = self.parse_stanza(self.stanza) if not self.identities: # ejabberd doesn't send identities when we browse online users # see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225 self.identities = [{'category': 'server', 'type': 'im', 'name': self.node}] self.get_jid_resource() return True @classmethod def parse_stanza(cls, stanza): identities, features, data, node = [], [], [], None q = stanza.getTag('query') node = q.getAttr('node') if not node: node = '' qc = stanza.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': var = i.getAttr('var') if var: features.append(var) elif i.getName() == 'x' and i.getNamespace() == nbxmpp.NS_DATA: data.append(nbxmpp.DataForm(node=i)) return identities, features, data, node class AgentInfoErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'agent-info-error-received' base_network_events = [] def generate(self): self.get_jid_resource() self.get_id() if self.id_ in self.conn.disco_info_ids: self.conn.disco_info_ids.remove(self.id_) return True class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'file-request-received' base_network_events = [] def init(self): self.jingle_content = None self.FT_content = None def generate(self): self.get_id() self.fjid = self.conn._ft_get_from(self.stanza) self.jid = app.get_jid_without_resource(self.fjid) if self.jingle_content: secu = self.jingle_content.getTag('security') self.FT_content.use_security = bool(secu) if secu: fingerprint = secu.getTag('fingerprint') if fingerprint: self.FT_content.x509_fingerprint = fingerprint.getData() if not self.FT_content.transport: self.FT_content.transport = JingleTransportSocks5() self.FT_content.transport.set_our_jid( self.FT_content.session.ourjid) self.FT_content.transport.set_connection( self.FT_content.session.connection) sid = self.stanza.getTag('jingle').getAttr('sid') self.file_props = FilesProp.getNewFileProp(self.conn.name, sid) self.file_props.transport_sid = self.FT_content.transport.sid self.FT_content.file_props = self.file_props self.FT_content.transport.set_file_props(self.file_props) self.file_props.streamhosts.extend( self.FT_content.transport.remote_candidates) for host in self.file_props.streamhosts: host['initiator'] = self.FT_content.session.initiator host['target'] = self.FT_content.session.responder self.file_props.session_type = 'jingle' self.file_props.stream_methods = nbxmpp.NS_BYTESTREAM desc = self.jingle_content.getTag('description') if self.jingle_content.getAttr('creator') == 'initiator': file_tag = desc.getTag('file') self.file_props.sender = self.fjid self.file_props.receiver = self.conn._ft_get_our_jid() else: file_tag = desc.getTag('file') h = file_tag.getTag('hash') h = h.getData() if h else None n = file_tag.getTag('name') n = n.getData() if n else None pjid = app.get_jid_without_resource(self.fjid) file_info = self.conn.get_file_info(pjid, hash_=h, name=n,account=self.conn.name) self.file_props.file_name = file_info['file-name'] self.file_props.sender = self.conn._ft_get_our_jid() self.file_props.receiver = self.fjid self.file_props.type_ = 's' for child in file_tag.getChildren(): name = child.getName() val = child.getData() if val is None: continue if name == 'name': self.file_props.name = val if name == 'size': self.file_props.size = int(val) if name == 'hash': self.file_props.algo = child.getAttr('algo') self.file_props.hash_ = val if name == 'date': self.file_props.date = val else: si = self.stanza.getTag('si') self.file_props = FilesProp.getNewFileProp(self.conn.name, si.getAttr('id')) self.file_props.transport_sid = self.file_props.sid profile = si.getAttr('profile') if profile != nbxmpp.NS_FILE: self.conn.send_file_rejection(self.file_props, code='400', typ='profile') raise nbxmpp.NodeProcessed feature_tag = si.getTag('feature', namespace=nbxmpp.NS_FEATURE) if not feature_tag: return form_tag = feature_tag.getTag('x', namespace=nbxmpp.NS_DATA) if not form_tag: return self.dataform = dataforms.ExtendForm(node=form_tag) for f in self.dataform.iter_fields(): if f.var == 'stream-method' and f.type_ == 'list-single': values = [o[1] for o in f.options] self.file_props.stream_methods = ' '.join(values) if nbxmpp.NS_BYTESTREAM in values or \ nbxmpp.NS_IBB in values: break else: self.conn.send_file_rejection(self.file_props, code='400', typ='stream') raise nbxmpp.NodeProcessed file_tag = si.getTag('file') for name, val in file_tag.getAttrs().items(): if val is None: continue if name == 'name': self.file_props.name = val if name == 'size': self.file_props.size = int(val) mime_type = si.getAttr('mime-type') if mime_type is not None: self.file_props.mime_type = mime_type self.file_props.sender = self.fjid self.file_props.receiver = self.conn._ft_get_our_jid() self.file_props.request_id = self.id_ file_desc_tag = file_tag.getTag('desc') if file_desc_tag is not None: self.file_props.desc = file_desc_tag.getData() self.file_props.transfered_size = [] return True class FileRequestErrorEvent(nec.NetworkIncomingEvent): name = 'file-request-error' base_network_events = [] def generate(self): self.jid = app.get_jid_without_resource(self.jid) return True class FileTransferCompletedEvent(nec.NetworkIncomingEvent): name = 'file-transfer-completed' base_network_events = [] def generate(self): jid = str(self.file_props.receiver) self.jid = app.get_jid_without_resource(jid) return True class GatewayPromptReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): name = 'gateway-prompt-received' base_network_events = [] def generate(self): self.get_jid_resource() query = self.stanza.getTag('query') if query: self.desc = query.getTagData('desc') self.prompt = query.getTagData('prompt') self.prompt_jid = query.getTagData('jid') else: self.desc = None self.prompt = None self.prompt_jid = None return True class NotificationEvent(nec.NetworkIncomingEvent): name = 'notification' base_network_events = ['decrypted-message-received', 'gc-message-received', 'presence-received'] def generate(self): # what's needed to compute output self.account = self.base_event.conn.name self.conn = self.base_event.conn self.jid = '' self.control = None self.control_focused = False self.first_unread = False # For output self.do_sound = False self.sound_file = '' self.sound_event = '' # gajim sound played if not sound_file is set self.show_popup = False self.do_popup = False self.popup_title = '' self.popup_text = '' self.popup_event_type = '' self.popup_msg_type = '' self.icon_name = None self.transport_name = None self.show = None self.popup_timeout = -1 self.do_command = False self.command = '' self.show_in_notification_area = False self.show_in_roster = False self.detect_type() if self.notif_type == 'msg': self.handle_incoming_msg_event(self.base_event) elif self.notif_type == 'gc-msg': self.handle_incoming_gc_msg_event(self.base_event) elif self.notif_type == 'pres': self.handle_incoming_pres_event(self.base_event) return True def detect_type(self): if self.base_event.name == 'decrypted-message-received': self.notif_type = 'msg' if self.base_event.name == 'gc-message-received': self.notif_type = 'gc-msg' if self.base_event.name == 'presence-received': self.notif_type = 'pres' def handle_incoming_msg_event(self, msg_obj): # don't alert for carbon copied messages from ourselves if msg_obj.sent: return if not msg_obj.msgtxt: return self.jid = msg_obj.jid if msg_obj.mtype == 'pm': self.jid = msg_obj.fjid self.control = app.interface.msg_win_mgr.search_control( msg_obj.jid, self.account, msg_obj.resource) if self.control is None: if len(app.events.get_events( self.account, msg_obj.jid, [msg_obj.mtype])) <= 1: self.first_unread = True else: self.control_focused = self.control.has_focus() if msg_obj.mtype == 'pm': nick = msg_obj.resource else: nick = app.get_name_from_jid(self.conn.name, self.jid) if self.first_unread: self.sound_event = 'first_message_received' elif self.control_focused: self.sound_event = 'next_message_received_focused' else: self.sound_event = 'next_message_received_unfocused' if app.config.get('notification_preview_message'): self.popup_text = msg_obj.msgtxt if self.popup_text and (self.popup_text.startswith('/me ') or \ self.popup_text.startswith('/me\n')): self.popup_text = '* ' + nick + self.popup_text[3:] else: # We don't want message preview, do_preview = False self.popup_text = '' if msg_obj.mtype == 'normal': # single message self.popup_msg_type = 'normal' self.popup_event_type = _('New Single Message') self.popup_title = _('New Single Message from %(nickname)s') % \ {'nickname': nick} elif msg_obj.mtype == 'pm': self.popup_msg_type = 'pm' self.popup_event_type = _('New Private Message') self.popup_title = _('New Private Message from group chat %s') % \ msg_obj.jid if self.popup_text: self.popup_text = _('%(nickname)s: %(message)s') % \ {'nickname': nick, 'message': self.popup_text} else: self.popup_text = _('Messaged by %(nickname)s') % \ {'nickname': nick} else: # chat message self.popup_msg_type = 'chat' self.popup_event_type = _('New Message') self.popup_title = _('New Message from %(nickname)s') % \ {'nickname': nick} if app.config.get('notify_on_new_message'): if self.first_unread or (app.config.get('autopopup_chat_opened') \ and not self.control_focused): if app.config.get('autopopupaway'): # always show notification self.do_popup = True if app.connections[self.conn.name].connected in (2, 3): # we're online or chat self.do_popup = True if msg_obj.attention and not app.config.get( 'ignore_incoming_attention'): self.popup_timeout = 0 self.do_popup = True else: self.popup_timeout = app.config.get('notification_timeout') if msg_obj.attention and not app.config.get( 'ignore_incoming_attention') and app.config.get_per('soundevents', 'attention_received', 'enabled'): self.sound_event = 'attention_received' self.do_sound = True elif self.first_unread and helpers.allow_sound_notification( self.conn.name, 'first_message_received'): self.do_sound = True elif not self.first_unread and self.control_focused and \ helpers.allow_sound_notification(self.conn.name, 'next_message_received_focused'): self.do_sound = True elif not self.first_unread and not self.control_focused and \ helpers.allow_sound_notification(self.conn.name, 'next_message_received_unfocused'): self.do_sound = True def handle_incoming_gc_msg_event(self, msg_obj): if not msg_obj.msg_obj.gc_control: # we got a message from a room we're not in? ignore it return self.jid = msg_obj.jid sound = msg_obj.msg_obj.gc_control.highlighting_for_message( msg_obj.msgtxt, msg_obj.timestamp)[1] if msg_obj.nickname != msg_obj.msg_obj.gc_control.nick: self.do_sound = True if sound == 'received': self.sound_event = 'muc_message_received' elif sound == 'highlight': self.sound_event = 'muc_message_highlight' else: self.do_sound = False else: self.do_sound = False self.do_popup = False def handle_incoming_pres_event(self, pres_obj): if app.jid_is_transport(pres_obj.jid): return True account = pres_obj.conn.name self.jid = pres_obj.jid resource = pres_obj.resource or '' # It isn't an agent for c in pres_obj.contact_list: if c.resource == resource: # we look for other connected resources continue if c.show not in ('offline', 'error'): return True # no other resource is connected, let's look in metacontacts family = app.contacts.get_metacontacts_family(account, self.jid) for info in family: acct_ = info['account'] jid_ = info['jid'] c_ = app.contacts.get_contact_with_highest_priority(acct_, jid_) if not c_: continue if c_.jid == self.jid: continue if c_.show not in ('offline', 'error'): return True if pres_obj.old_show < 2 and pres_obj.new_show > 1: event = 'contact_connected' server = app.get_server_from_jid(self.jid) account_server = account + '/' + server block_transport = False if account_server in app.block_signed_in_notifications and \ app.block_signed_in_notifications[account_server]: block_transport = True if helpers.allow_showing_notification(account, 'notify_on_signin') \ and not app.block_signed_in_notifications[account] and \ not block_transport: self.do_popup = True if app.config.get_per('soundevents', 'contact_connected', 'enabled') and not app.block_signed_in_notifications[account] and\ not block_transport and helpers.allow_sound_notification(account, 'contact_connected'): self.sound_event = event self.do_sound = True elif pres_obj.old_show > 1 and pres_obj.new_show < 2: event = 'contact_disconnected' if helpers.allow_showing_notification(account, 'notify_on_signout'): self.do_popup = True if app.config.get_per('soundevents', 'contact_disconnected', 'enabled') and helpers.allow_sound_notification(account, event): self.sound_event = event self.do_sound = True # Status change (not connected/disconnected or error (<1)) elif pres_obj.new_show > 1: event = 'status_change' else: return True if app.jid_is_transport(self.jid): self.transport_name = app.get_transport_name_from_jid(self.jid) self.show = pres_obj.show self.popup_timeout = app.config.get('notification_timeout') nick = i18n.direction_mark + app.get_name_from_jid(account, self.jid) if event == 'status_change': self.popup_title = _('%(nick)s Changed Status') % \ {'nick': nick} self.popup_text = _('%(nick)s is now %(status)s') % \ {'nick': nick, 'status': helpers.get_uf_show(pres_obj.show)} if pres_obj.status: self.popup_text = self.popup_text + " : " + pres_obj.status self.popup_event_type = _('Contact Changed Status') elif event == 'contact_connected': self.popup_title = _('%(nickname)s Signed In') % {'nickname': nick} self.popup_text = '' if pres_obj.status: self.popup_text = pres_obj.status self.popup_event_type = _('Contact Signed In') elif event == 'contact_disconnected': self.popup_title = _('%(nickname)s Signed Out') % {'nickname': nick} self.popup_text = '' if pres_obj.status: self.popup_text = pres_obj.status self.popup_event_type = _('Contact Signed Out') class MessageOutgoingEvent(nec.NetworkOutgoingEvent): name = 'message-outgoing' base_network_events = [] def init(self): self.additional_data = {} self.message = None self.keyID = None self.type_ = 'chat' self.kind = None self.timestamp = None self.subject = '' self.chatstate = None self.stanza_id = None self.resource = None self.user_nick = None self.xhtml = None self.label = None self.session = None self.forward_from = None self.form_node = None self.delayed = None self.callback = None self.callback_args = [] self.now = False self.is_loggable = True self.control = None self.attention = False self.correct_id = None self.automatic_message = True self.encryption = '' self.encrypted = False def get_full_jid(self): if self.resource: return self.jid + '/' + self.resource if self.session: return self.session.get_to() return self.jid def generate(self): if self.type_ == 'chat': self.kind = KindConstant.CHAT_MSG_SENT else: self.kind = KindConstant.SINGLE_MSG_SENT return True class StanzaMessageOutgoingEvent(nec.NetworkOutgoingEvent): name='stanza-message-outgoing' base_network_events = [] def generate(self): return True class GcStanzaMessageOutgoingEvent(nec.NetworkOutgoingEvent): name='gc-stanza-message-outgoing' base_network_events = [] def generate(self): return True class GcMessageOutgoingEvent(nec.NetworkOutgoingEvent): name = 'gc-message-outgoing' base_network_events = [] def init(self): self.additional_data = {} self.message = '' self.chatstate = None self.xhtml = None self.stanza_id = None self.label = None self.callback = None self.callback_args = [] self.is_loggable = True self.control = None self.correct_id = None self.automatic_message = True def generate(self): return True class ClientCertPassphraseEvent(nec.NetworkIncomingEvent): name = 'client-cert-passphrase' base_network_events = [] class InformationEvent(nec.NetworkIncomingEvent): name = 'information' base_network_events = [] def init(self): self.args = None self.kwargs = {} self.dialog_name = None self.popup = True def generate(self): if self.args is None: self.args = () else: self.args = (self.args,) return True class BlockingEvent(nec.NetworkIncomingEvent): name = 'blocking' base_network_events = [] def init(self): self.blocklist = [] self.blocked_jids = [] self.unblocked_jids = [] self.unblock_all = False def generate(self): block_list = self.stanza.getTag( 'blocklist', namespace=nbxmpp.NS_BLOCKING) if block_list is not None: for item in block_list.getTags('item'): self.blocklist.append(item.getAttr('jid')) app.log('blocking').info( 'Blocklist Received: %s', self.blocklist) return True block_tag = self.stanza.getTag('block', namespace=nbxmpp.NS_BLOCKING) if block_tag is not None: for item in block_tag.getTags('item'): self.blocked_jids.append(item.getAttr('jid')) app.log('blocking').info( 'Blocking Push - blocked JIDs: %s', self.blocked_jids) unblock_tag = self.stanza.getTag( 'unblock', namespace=nbxmpp.NS_BLOCKING) if unblock_tag is not None: if not unblock_tag.getTags('item'): self.unblock_all = True app.log('blocking').info('Blocking Push - unblocked all') return True for item in unblock_tag.getTags('item'): self.unblocked_jids.append(item.getAttr('jid')) app.log('blocking').info( 'Blocking Push - unblocked JIDs: %s', self.unblocked_jids) return True class HTTPUploadStartEvent(nec.NetworkIncomingEvent): name = 'httpupload-start' base_network_events = [] def generate(self): return True class HTTPUploadProgressEvent(nec.NetworkIncomingEvent): name = 'httpupload-progress' base_network_events = [] def generate(self): return True