1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 import os
32 import base64
33 import sys
34 import operator
35 import hashlib
36 import hmac
37
38 from time import (altzone, daylight, gmtime, localtime, mktime, strftime,
39 time as time_time, timezone, tzname)
40 from calendar import timegm
41 import datetime
42
43 import common.xmpp
44 import common.caps_cache as capscache
45
46 from common import helpers
47 from common import gajim
48 from common import exceptions
49 from common.commands import ConnectionCommands
50 from common.pubsub import ConnectionPubSub
51 from common.pep import ConnectionPEP
52 from common.protocol.caps import ConnectionCaps
53 from common.protocol.bytestream import ConnectionSocks5Bytestream
54 from common import ged
55 from common import nec
56 from common.nec import NetworkEvent
57 from plugins import GajimPlugin
58 if gajim.HAVE_FARSIGHT:
59 from common.jingle import ConnectionJingle
60 else:
66
67 from common import dbus_support
68 if dbus_support.supported:
69 import dbus
70 from music_track_listener import MusicTrackListener
71
72 import logging
73 log = logging.getLogger('gajim.c.connection_handlers')
74
75
76 VCARD_PUBLISHED = 'vcard_published'
77 VCARD_ARRIVED = 'vcard_arrived'
78 AGENT_REMOVED = 'agent_removed'
79 METACONTACTS_ARRIVED = 'metacontacts_arrived'
80 ROSTER_ARRIVED = 'roster_arrived'
81 PRIVACY_ARRIVED = 'privacy_arrived'
82 PEP_CONFIG = 'pep_config'
83 HAS_IDLE = True
84 try:
85
86 import common.sleepy
87 except Exception:
88 log.debug(_('Unable to load idle module'))
89 HAS_IDLE = False
90
91
93 """
94 Holds xmpppy handlers and public methods for discover services
95 """
96
98 """
99 According to XEP-0030:
100 jid is mandatory;
101 name, node, action is optional.
102 """
103 self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix)
104
105 - def discoverInfo(self, jid, node = None, id_prefix = None):
106 """
107 According to XEP-0030:
108 For identity: category, type is mandatory, name is optional.
109 For feature: var is mandatory.
110 """
111 self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix)
112
125
127 if resp.getType() == 'result':
128 self.dispatch('INFORMATION', (_('Registration succeeded'),
129 _('Registration with agent %s succeeded') % agent))
130 self.request_subscription(agent, auto_auth=True)
131 self.agent_registrations[agent]['roster_push'] = True
132 if self.agent_registrations[agent]['sub_received']:
133 p = common.xmpp.Presence(agent, 'subscribed')
134 p = self.add_sha(p)
135 self.connection.send(p)
136 if resp.getType() == 'error':
137 self.dispatch('ERROR', (_('Registration failed'), _('Registration with'
138 ' agent %(agent)s failed with error %(error)s: %(error_msg)s') % {
139 'agent': agent, 'error': resp.getError(),
140 'error_msg': resp.getErrorMsg()}))
141
143 if not self.connection or self.connected < 2:
144 return
145 if is_form:
146 iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=agent)
147 query = iq.getTag('query')
148 info.setAttr('type', 'submit')
149 query.addChild(node=info)
150 self.connection.SendAndCallForResponse(iq, self._agent_registered_cb,
151 {'agent': agent})
152 else:
153
154 common.xmpp.features_nb.register(self.connection, agent, info,
155 self._agent_registered_cb, {'agent': agent})
156 self.agent_registrations[agent] = {'roster_push': False,
157 'sub_received': False}
158
159 - def _discover(self, ns, jid, node=None, id_prefix=None):
169
173
175 """
176 Get disco info
177 """
178 if not self.connection or self.connected < 2:
179 return
180 frm = helpers.get_full_jid_from_iq(iq_obj)
181 to = unicode(iq_obj.getAttr('to'))
182 id_ = unicode(iq_obj.getAttr('id'))
183 iq = common.xmpp.Iq(to=frm, typ='result', queryNS=common.xmpp.NS_DISCO,
184 frm=to)
185 iq.setAttr('id', id_)
186 query = iq.setTag('query')
187 query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[0])
188 for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI,
189 common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS):
190 feature = common.xmpp.Node('feature')
191 feature.setAttr('var', f)
192 query.addChild(node=feature)
193
194 self.connection.send(iq)
195 raise common.xmpp.NodeProcessed
196
201
235
252
284
294
296 log.debug('DiscoverInfoCB')
297 if not self.connection or self.connected < 2:
298 return
299
300
301
302 identities, features, data = [], [], []
303 q = iq_obj.getTag('query')
304 node = q.getAttr('node')
305 if not node:
306 node = ''
307 qc = iq_obj.getQueryChildren()
308 if not qc:
309 qc = []
310 is_muc = False
311 transport_type = ''
312 for i in qc:
313 if i.getName() == 'identity':
314 attr = {}
315 for key in i.getAttrs().keys():
316 attr[key] = i.getAttr(key)
317 if 'category' in attr and \
318 attr['category'] in ('gateway', 'headline') and \
319 'type' in attr:
320 transport_type = attr['type']
321 if 'category' in attr and \
322 attr['category'] == 'conference' and \
323 'type' in attr and attr['type'] == 'text':
324 is_muc = True
325 identities.append(attr)
326 elif i.getName() == 'feature':
327 var = i.getAttr('var')
328 if var:
329 features.append(var)
330 elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA:
331 data.append(common.xmpp.DataForm(node=i))
332 jid = helpers.get_full_jid_from_iq(iq_obj)
333 if transport_type and jid not in gajim.transport_type:
334 gajim.transport_type[jid] = transport_type
335 gajim.logger.save_transport_type(jid, transport_type)
336 id_ = iq_obj.getID()
337 if id_ is None:
338 log.warn('Invalid IQ received without an ID. Ignoring it: %s' % iq_obj)
339 return
340 if not identities:
341
342 identities = [{'category': 'server', 'type': 'im', 'name': node}]
343 if id_[:6] == 'Gajim_':
344 if jid == gajim.config.get_per('accounts', self.name, 'hostname'):
345 if features.__contains__(common.xmpp.NS_GMAILNOTIFY):
346 gajim.gmail_domains.append(jid)
347 self.request_gmail_notifications()
348 if features.__contains__(common.xmpp.NS_SECLABEL):
349 self.seclabel_supported = True
350 for identity in identities:
351 if identity['category'] == 'pubsub' and identity.get('type') == \
352 'pep':
353 self.pep_supported = True
354 break
355 if features.__contains__(common.xmpp.NS_VCARD):
356 self.vcard_supported = True
357 if features.__contains__(common.xmpp.NS_PUBSUB):
358 self.pubsub_supported = True
359 if features.__contains__(common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS):
360 self.pubsub_publish_options_supported = True
361 else:
362
363 our_jid = gajim.get_jid_from_account(self.name)
364 self.send_pb_purge(our_jid, 'storage:bookmarks')
365 self.send_pb_delete(our_jid, 'storage:bookmarks')
366 if features.__contains__(common.xmpp.NS_BYTESTREAM):
367 our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\
368 '/' + self.server_resource)
369 gajim.proxy65_manager.resolve(jid, self.connection, our_jid,
370 self.name)
371 if features.__contains__(common.xmpp.NS_MUC) and is_muc:
372 type_ = transport_type or 'jabber'
373 self.muc_jid[type_] = jid
374 if transport_type:
375 if transport_type in self.available_transports:
376 self.available_transports[transport_type].append(jid)
377 else:
378 self.available_transports[transport_type] = [jid]
379 if not self.privacy_rules_requested:
380 self.privacy_rules_requested = True
381 self._request_privacy()
382
383 self.dispatch('AGENT_INFO_INFO', (jid, node, identities,
384 features, data))
385 self._capsDiscoCB(jid, node, identities, features, data)
386
389 self.vcard_sha = None
390 self.vcard_shas = {}
391 self.room_jids = []
392
393 - def add_sha(self, p, send_caps = True):
400
408
426
450
489
491 """
492 Request the VCARD
493
494 If groupchat_jid is not null, it means we request a vcard to a fake jid,
495 like in private messages in groupchat. jid can be the real jid of the
496 contact, but we want to consider it comes from a fake jid
497 """
498 if not self.connection or self.connected < 2:
499 return
500 iq = common.xmpp.Iq(typ = 'get')
501 if jid:
502 iq.setTo(jid)
503 iq.setTag(common.xmpp.NS_VCARD + ' vCard')
504
505 id_ = self.connection.getAnID()
506 iq.setID(id_)
507 j = jid
508 if not j:
509 j = gajim.get_jid_from_account(self.name)
510 self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid)
511 if groupchat_jid:
512 room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0]
513 if not room_jid in self.room_jids:
514 self.room_jids.append(room_jid)
515 self.groupchat_jids[id_] = groupchat_jid
516 self.connection.send(iq)
517
555
556 - def _IqCB(self, con, iq_obj):
557 id_ = iq_obj.getID()
558
559 gajim.nec.push_incoming_event(NetworkEvent('raw-iq-received',
560 conn=con, xmpp_iq=iq_obj))
561
562
563 found_tim = None
564 for tim in self.awaiting_timeouts:
565 if id_ == self.awaiting_timeouts[tim][0]:
566 found_tim = tim
567 break
568 if found_tim:
569 del self.awaiting_timeouts[found_tim]
570
571 if id_ not in self.awaiting_answers:
572 return
573 if self.awaiting_answers[id_][0] == VCARD_PUBLISHED:
574 if iq_obj.getType() == 'result':
575 vcard_iq = self.awaiting_answers[id_][1]
576
577 if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag('SHA'):
578 new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA')
579 else:
580 new_sha = ''
581
582
583 our_jid = gajim.get_jid_from_account(self.name)
584 self._save_vcard_to_hd(our_jid, vcard_iq)
585
586
587 if self.vcard_sha != new_sha and gajim.SHOW_LIST[self.connected] !=\
588 'invisible':
589 if not self.connection or self.connected < 2:
590 return
591 self.vcard_sha = new_sha
592 sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
593 p = common.xmpp.Presence(typ = None, priority = self.priority,
594 show = sshow, status = self.status)
595 p = self.add_sha(p)
596 self.connection.send(p)
597 self.dispatch('VCARD_PUBLISHED', ())
598 elif iq_obj.getType() == 'error':
599 self.dispatch('VCARD_NOT_PUBLISHED', ())
600 elif self.awaiting_answers[id_][0] == VCARD_ARRIVED:
601
602
603 jid = self.awaiting_answers[id_][1]
604 groupchat_jid = self.awaiting_answers[id_][2]
605 frm = jid
606 if groupchat_jid:
607
608 frm = groupchat_jid
609 our_jid = gajim.get_jid_from_account(self.name)
610 if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error':
611 if frm and frm != our_jid:
612
613 self._save_vcard_to_hd(frm, '')
614 jid, resource = gajim.get_room_and_nick_from_fjid(frm)
615 self.dispatch('VCARD', {'jid': jid, 'resource': resource})
616 elif frm == our_jid:
617 self.dispatch('MYVCARD', {'jid': frm})
618 elif self.awaiting_answers[id_][0] == AGENT_REMOVED:
619 jid = self.awaiting_answers[id_][1]
620 self.dispatch('AGENT_REMOVED', jid)
621 elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED:
622 if not self.connection:
623 return
624 if iq_obj.getType() == 'result':
625
626
627 meta_list = {}
628 query = iq_obj.getTag('query')
629 storage = query.getTag('storage')
630 metas = storage.getTags('meta')
631 for meta in metas:
632 try:
633 jid = helpers.parse_jid(meta.getAttr('jid'))
634 except common.helpers.InvalidFormat:
635 continue
636 tag = meta.getAttr('tag')
637 data = {'jid': jid}
638 order = meta.getAttr('order')
639 try:
640 order = int(order)
641 except Exception:
642 order = 0
643 if order is not None:
644 data['order'] = order
645 if tag in meta_list:
646 meta_list[tag].append(data)
647 else:
648 meta_list[tag] = [data]
649 self.dispatch('METACONTACTS', meta_list)
650 else:
651 if iq_obj.getErrorCode() not in ('403', '406', '404'):
652 self.private_storage_supported = False
653
654
655 version = None
656 if con.Stream.features and con.Stream.features.getTag('ver',
657 namespace=common.xmpp.NS_ROSTER_VER):
658 version = gajim.config.get_per('accounts', self.name,
659 'roster_version')
660 if version and not gajim.contacts.get_contacts_jid_list(
661 self.name):
662 gajim.config.set_per('accounts', self.name,
663 'roster_version', '')
664 version = None
665
666 iq_id = self.connection.initRoster(version=version)
667 self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
668 elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED:
669 if iq_obj.getType() == 'result':
670 if not iq_obj.getTag('query'):
671 account_jid = gajim.get_jid_from_account(self.name)
672 roster_data = gajim.logger.get_roster(account_jid)
673 roster = self.connection.getRoster(force=True)
674 roster.setRaw(roster_data)
675 self._getRoster()
676 elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED:
677 if iq_obj.getType() != 'error':
678 self.privacy_rules_supported = True
679 self.get_privacy_list('block')
680 elif self.continue_connect_info:
681 if self.continue_connect_info[0] == 'invisible':
682
683 self.disconnect(on_purpose=True)
684 self.dispatch('STATUS', 'offline')
685 self.dispatch('ERROR', (_('Invisibility not supported'),
686 _('Account %s doesn\'t support invisibility.') % self.name))
687 return
688
689 self.get_metacontacts()
690 elif self.awaiting_answers[id_][0] == PEP_CONFIG:
691 if iq_obj.getType() == 'error':
692 return
693 if not iq_obj.getTag('pubsub'):
694 return
695 conf = iq_obj.getTag('pubsub').getTag('configure')
696 if not conf:
697 return
698 node = conf.getAttr('node')
699 form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA)
700 if form_tag:
701 form = common.dataforms.ExtendForm(node=form_tag)
702 self.dispatch('PEP_CONFIG', (node, form))
703
704 del self.awaiting_answers[id_]
705
707 """
708 Called when we receive a vCard Parse the vCard and send it to plugins
709 """
710 if not vc.getTag('vCard'):
711 return
712 if not vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD:
713 return
714 id_ = vc.getID()
715 frm_iq = vc.getFrom()
716 our_jid = gajim.get_jid_from_account(self.name)
717 resource = ''
718 if id_ in self.groupchat_jids:
719 who = self.groupchat_jids[id_]
720 frm, resource = gajim.get_room_and_nick_from_fjid(who)
721 del self.groupchat_jids[id_]
722 elif frm_iq:
723 who = helpers.get_full_jid_from_iq(vc)
724 frm, resource = gajim.get_room_and_nick_from_fjid(who)
725 else:
726 who = frm = our_jid
727 card = vc.getChildren()[0]
728 vcard = self._node_to_dict(card)
729 photo_decoded = None
730 if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
731 'BINVAL' in vcard['PHOTO']:
732 photo = vcard['PHOTO']['BINVAL']
733 try:
734 photo_decoded = base64.decodestring(photo)
735 avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
736 except Exception:
737 avatar_sha = ''
738 else:
739 avatar_sha = ''
740
741 if avatar_sha:
742 card.getTag('PHOTO').setTagData('SHA', avatar_sha)
743
744
745 self._save_vcard_to_hd(who, card)
746
747 puny_jid = helpers.sanitize_filename(frm)
748 puny_nick = None
749 begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid)
750 frm_jid = frm
751 if frm in self.room_jids:
752 puny_nick = helpers.sanitize_filename(resource)
753
754 if not os.path.isdir(begin_path):
755 os.mkdir(begin_path, 0700)
756 begin_path = os.path.join(begin_path, puny_nick)
757 frm_jid += '/' + resource
758 if photo_decoded:
759 avatar_file = begin_path + '_notif_size_colored.png'
760 if frm_jid == our_jid and avatar_sha != self.vcard_sha:
761 gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
762 elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \
763 frm_jid not in self.vcard_shas or \
764 avatar_sha != self.vcard_shas[frm_jid]):
765 gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
766 if avatar_sha:
767 self.vcard_shas[frm_jid] = avatar_sha
768 elif frm in self.vcard_shas:
769 del self.vcard_shas[frm]
770 else:
771 for ext in ('.jpeg', '.png', '_notif_size_bw.png',
772 '_notif_size_colored.png'):
773 path = begin_path + ext
774 if os.path.isfile(path):
775 os.remove(path)
776
777 vcard['jid'] = frm
778 vcard['resource'] = resource
779 if frm_jid == our_jid:
780 self.dispatch('MYVCARD', vcard)
781
782
783 if self.vcard_sha == avatar_sha:
784 return
785 self.vcard_sha = avatar_sha
786 if gajim.SHOW_LIST[self.connected] == 'invisible':
787 return
788 if not self.connection:
789 return
790 sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
791 p = common.xmpp.Presence(typ = None, priority = self.priority,
792 show = sshow, status = self.status)
793 p = self.add_sha(p)
794 self.connection.send(p)
795 else:
796
797 self.dispatch('VCARD', vcard)
798
799
802
803 self.awaiting_answers = {}
804
805
806 self.awaiting_timeouts = {}
807
808
809 self.automatically_added = []
810
811 self.last_ids = []
812
813
814 self.sessions = {}
815
823
828
840
862
864 try:
865 if not thread_id:
866 return self.find_null_session(jid)
867 else:
868 return self.sessions[jid][thread_id]
869 except KeyError:
870 return None
871
873 """
874 Send termination messages and delete all active sessions
875 """
876 for jid in self.sessions:
877 for thread_id in self.sessions[jid]:
878 self.sessions[jid][thread_id].terminate(send_termination)
879
880 self.sessions = {}
881
883 if not jid in self.sessions:
884 jid = gajim.get_jid_without_resource(jid)
885 if not jid in self.sessions:
886 return
887
888 del self.sessions[jid][thread_id]
889
890 if not self.sessions[jid]:
891 del self.sessions[jid]
892
894 """
895 Find all of the sessions between us and a remote jid in which we haven't
896 received a thread_id yet and returns the session that we last sent a
897 message to
898 """
899 sessions = self.sessions[jid].values()
900
901
902 idless = [s for s in sessions if not s.received_thread_id]
903
904
905 chat_sessions = [s for s in idless if isinstance(s,
906 gajim.default_session_type)]
907
908 if chat_sessions:
909
910 return sorted(chat_sessions, key=operator.attrgetter("last_send"))[-1]
911 else:
912 return None
913
915 """
916 Find an active session that doesn't have a control attached
917 """
918 try:
919 sessions = self.sessions[jid].values()
920
921
922 chat_sessions = [s for s in sessions if isinstance(s,
923 gajim.default_session_type)]
924
925 orphaned = [s for s in chat_sessions if not s.control]
926
927 if resource:
928 orphaned = [s for s in orphaned if s.resource == resource]
929
930 return orphaned[0]
931 except (KeyError, IndexError):
932 return None
933
935 """
936 Create and register a new session
937
938 thread_id=None to generate one.
939 type_ should be 'chat' or 'pm'.
940 """
941 if not cls:
942 cls = gajim.default_session_type
943
944 sess = cls(self, common.xmpp.JID(jid), thread_id, type_)
945
946
947
948 if not type_ == 'pm':
949 jid = gajim.get_jid_without_resource(jid)
950
951 if not jid in self.sessions:
952 self.sessions[jid] = {}
953
954 self.sessions[jid][sess.thread_id] = sess
955
956 return sess
957
958 -class ConnectionHandlers(ConnectionVcard, ConnectionSocks5Bytestream,
959 ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP,
960 ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
999
1013
1020
1026
1028 log.debug('ErrorCB')
1029 if ConnectionHandlersBase._ErrorCB(self, con, iq_obj):
1030 return
1031 id_ = unicode(iq_obj.getID())
1032 if id_ in self.version_ids:
1033 gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
1034 conn=self, iq_obj=iq_obj))
1035 return
1036 if id_ in self.entity_time_ids:
1037 gajim.nec.push_incoming_event(LastResultReceivedEvent(None,
1038 conn=self, iq_obj=iq_obj))
1039 return
1040 jid_from = helpers.get_full_jid_from_iq(iq_obj)
1041 errmsg = iq_obj.getErrorMsg()
1042 errcode = iq_obj.getErrorCode()
1043 self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode))
1044
1073
1075 """
1076 Security Label callback, used for catalogues.
1077 """
1078 log.debug('SecLabelCB')
1079 query = iq_obj.getTag('catalog')
1080 to = query.getAttr('to')
1081 items = query.getTags('securitylabel')
1082 labels = {}
1083 ll = []
1084 for item in items:
1085 label = item.getTag('displaymarking').getData()
1086 labels[label] = item
1087 ll.append(label)
1088 if to not in self.seclabel_catalogues:
1089 self.seclabel_catalogues[to] = [[], None, None]
1090 self.seclabel_catalogues[to][1] = labels
1091 self.seclabel_catalogues[to][2] = ll
1092 for callback in self.seclabel_catalogues[to][0]:
1093 callback()
1094 self.seclabel_catalogues[to][0] = []
1095
1097 if to not in self.seclabel_catalogues:
1098 self.seclabel_catalogues[to] = [[], None, None]
1099 self.seclabel_catalogues[to][0].append(callback)
1100
1102 """
1103 storage_type can be 'pubsub' or 'xml' to tell from where we got bookmarks
1104 """
1105
1106
1107 resend_to_pubsub = False
1108 confs = storage.getTags('conference')
1109 for conf in confs:
1110 autojoin_val = conf.getAttr('autojoin')
1111 if autojoin_val is None:
1112 autojoin_val = False
1113 minimize_val = conf.getAttr('minimize')
1114 if minimize_val is None:
1115 minimize_val = False
1116 print_status = conf.getTagData('print_status')
1117 if not print_status:
1118 print_status = conf.getTagData('show_status')
1119 try:
1120 bm = {'name': conf.getAttr('name'),
1121 'jid': helpers.parse_jid(conf.getAttr('jid')),
1122 'autojoin': autojoin_val,
1123 'minimize': minimize_val,
1124 'password': conf.getTagData('password'),
1125 'nick': conf.getTagData('nick'),
1126 'print_status': print_status}
1127 except common.helpers.InvalidFormat:
1128 log.warn('Invalid JID: %s, ignoring it' % conf.getAttr('jid'))
1129 continue
1130
1131 bm_jids = [b['jid'] for b in self.bookmarks]
1132 if bm['jid'] not in bm_jids:
1133 self.bookmarks.append(bm)
1134 if storage_type == 'xml':
1135
1136 resend_to_pubsub = True
1137 self.dispatch('BOOKMARKS', self.bookmarks)
1138 if storage_type == 'pubsub':
1139
1140 self.get_bookmarks(storage_type='xml')
1141 if self.pubsub_supported and resend_to_pubsub:
1142 self.store_bookmarks('pubsub')
1143
1145 log.debug('rosterSetCB')
1146 version = iq_obj.getTagAttr('query', 'ver')
1147 for item in iq_obj.getTag('query').getChildren():
1148 try:
1149 jid = helpers.parse_jid(item.getAttr('jid'))
1150 except common.helpers.InvalidFormat:
1151 log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
1152 continue
1153 name = item.getAttr('name')
1154 sub = item.getAttr('subscription')
1155 ask = item.getAttr('ask')
1156 groups = []
1157 for group in item.getTags('group'):
1158 groups.append(group.getData())
1159 self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups))
1160 account_jid = gajim.get_jid_from_account(self.name)
1161 gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask,
1162 groups)
1163 if version:
1164 gajim.config.set_per('accounts', self.name, 'roster_version',
1165 version)
1166 if not self.connection or self.connected < 2:
1167 raise common.xmpp.NodeProcessed
1168 reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()},
1169 to=iq_obj.getFrom(), frm=iq_obj.getTo(), xmlns=None)
1170 self.connection.send(reply)
1171 raise common.xmpp.NodeProcessed
1172
1186
1204
1209
1222
1237
1242
1266
1276
1285
1287 """
1288 Called when we receive a message
1289 """
1290 log.debug('MessageCB')
1291
1292 gajim.nec.push_incoming_event(NetworkEvent('raw-message-received',
1293 conn=con, xmpp_msg=msg, account=self.name))
1294
1295 mtype = msg.getType()
1296
1297
1298 if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX):
1299 self._rosterItemExchangeCB(con, msg)
1300 return
1301
1302
1303 if msg.getTag('confirm', namespace=common.xmpp.NS_HTTP_AUTH):
1304 self._HttpAuthCB(con, msg)
1305 return
1306
1307 try:
1308 frm = helpers.get_full_jid_from_iq(msg)
1309 jid = helpers.get_jid_from_iq(msg)
1310 except helpers.InvalidFormat:
1311 self.dispatch('ERROR', (_('Invalid Jabber ID'),
1312 _('A message from a non-valid JID arrived, it has been ignored.')))
1313 return
1314
1315 addressTag = msg.getTag('addresses', namespace = common.xmpp.NS_ADDRESS)
1316
1317
1318 if addressTag and jid == gajim.get_jid_from_account(self.name):
1319 address = addressTag.getTag('address', attrs={'type': 'ofrom'})
1320 if address:
1321 try:
1322 frm = helpers.parse_jid(address.getAttr('jid'))
1323 except common.helpers.InvalidFormat:
1324 log.warn('Invalid JID: %s, ignoring it' % address.getAttr('jid'))
1325 return
1326 jid = gajim.get_jid_without_resource(frm)
1327
1328
1329 invite = None
1330 encTag = msg.getTag('x', namespace=common.xmpp.NS_ENCRYPTED)
1331
1332 if not encTag:
1333 invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
1334 if invite and not invite.getTag('invite'):
1335 invite = None
1336
1337
1338
1339
1340 xtags = msg.getTags('x')
1341 for xtag in xtags:
1342 if xtag.getNamespace() == common.xmpp.NS_CONFERENCE and not invite:
1343 try:
1344 room_jid = helpers.parse_jid(xtag.getAttr('jid'))
1345 except common.helpers.InvalidFormat:
1346 log.warn('Invalid JID: %s, ignoring it' % xtag.getAttr('jid'))
1347 continue
1348 is_continued = False
1349 if xtag.getTag('continue'):
1350 is_continued = True
1351 self.dispatch('GC_INVITATION', (room_jid, frm, '', None,
1352 is_continued))
1353 return
1354
1355 thread_id = msg.getThread()
1356
1357 if not mtype or mtype not in ('chat', 'groupchat', 'error'):
1358 mtype = 'normal'
1359
1360 msgtxt = msg.getBody()
1361
1362 encrypted = False
1363 xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO)
1364
1365 session = None
1366 gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, self.name)
1367 if not gc_control and \
1368 jid in gajim.interface.minimized_controls[self.name]:
1369 gc_control = gajim.interface.minimized_controls[self.name][jid]
1370
1371 if gc_control and jid == frm:
1372 mtype = 'groupchat'
1373
1374 if mtype != 'groupchat':
1375 session = self.get_or_create_session(frm, thread_id)
1376
1377 if thread_id and not session.received_thread_id:
1378 session.received_thread_id = True
1379
1380 session.last_receive = time_time()
1381
1382
1383 if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE):
1384 if gajim.HAVE_PYCRYPTO:
1385 feature = msg.getTag(name='feature', namespace=common.xmpp.NS_FEATURE)
1386 form = common.xmpp.DataForm(node=feature.getTag('x'))
1387
1388 if form['FORM_TYPE'] == 'urn:xmpp:ssn':
1389 session.handle_negotiation(form)
1390 else:
1391 reply = msg.buildReply()
1392 reply.setType('error')
1393
1394 reply.addChild(feature)
1395 err = common.xmpp.ErrorNode('service-unavailable', typ='cancel')
1396 reply.addChild(node=err)
1397
1398 con.send(reply)
1399
1400 raise common.xmpp.NodeProcessed
1401
1402 return
1403
1404 if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT):
1405 init = msg.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT)
1406 form = common.xmpp.DataForm(node=init.getTag('x'))
1407
1408 session.handle_negotiation(form)
1409
1410 raise common.xmpp.NodeProcessed
1411
1412 tim = msg.getTimestamp()
1413 tim = helpers.datetime_tuple(tim)
1414 tim = localtime(timegm(tim))
1415
1416 if xep_200_encrypted:
1417 encrypted = 'xep200'
1418
1419 try:
1420 msg = session.decrypt_stanza(msg)
1421 msgtxt = msg.getBody()
1422 except Exception:
1423 self.dispatch('FAILED_DECRYPT', (frm, tim, session))
1424
1425
1426
1427 contact = gajim.contacts.get_contact(self.name, jid)
1428 nick = gajim.get_room_and_nick_from_fjid(frm)[1]
1429 gc_contact = gajim.contacts.get_gc_contact(self.name, jid, nick)
1430 if msg.getTag('request', namespace=common.xmpp.NS_RECEIPTS) \
1431 and gajim.config.get_per('accounts', self.name,
1432 'answer_receipts') and ((contact and contact.sub \
1433 not in (u'to', u'none')) or gc_contact) and mtype != 'error':
1434 receipt = common.xmpp.Message(to=frm, typ='chat')
1435 receipt.setID(msg.getID())
1436 receipt.setTag('received',
1437 namespace='urn:xmpp:receipts')
1438
1439 if thread_id:
1440 receipt.setThread(thread_id)
1441 con.send(receipt)
1442
1443
1444 if msg.getTag('received', namespace=common.xmpp.NS_RECEIPTS) and \
1445 session.control and gajim.config.get_per('accounts', self.name,
1446 'request_receipt'):
1447 session.control.conv_textview.hide_xep0184_warning(msg.getID())
1448
1449 if encTag and self.USE_GPG:
1450 encmsg = encTag.getData()
1451
1452 keyID = gajim.config.get_per('accounts', self.name, 'keyid')
1453 if keyID:
1454 def decrypt_thread(encmsg, keyID):
1455 decmsg = self.gpg.decrypt(encmsg, keyID)
1456
1457 msgtxt = helpers.decode_string(decmsg.replace('\x00', ''))
1458 encrypted = 'xep27'
1459 return (msgtxt, encrypted)
1460 gajim.thread_interface(decrypt_thread, [encmsg, keyID],
1461 self._on_message_decrypted, [mtype, msg, session, frm, jid,
1462 invite, tim])
1463 return
1464 self._on_message_decrypted((msgtxt, encrypted), mtype, msg, session, frm,
1465 jid, invite, tim)
1466
1469 msgtxt, encrypted = output
1470 if mtype == 'error':
1471 self.dispatch_error_message(msg, msgtxt, session, frm, tim)
1472 elif mtype == 'groupchat':
1473 self.dispatch_gc_message(msg, frm, msgtxt, jid, tim)
1474 elif invite is not None:
1475 self.dispatch_invite_message(invite, frm)
1476 else:
1477 if isinstance(session, gajim.default_session_type):
1478 session.received(frm, msgtxt, tim, encrypted, msg)
1479 else:
1480 session.received(msg)
1481
1482
1483
1485 error_msg = msg.getErrorMsg()
1486
1487 if not error_msg:
1488 error_msg = msgtxt
1489 msgtxt = None
1490
1491 subject = msg.getSubject()
1492
1493 if session.is_loggable():
1494 try:
1495 gajim.logger.write('error', frm, error_msg, tim=tim,
1496 subject=subject)
1497 except exceptions.PysqliteOperationalError, e:
1498 self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
1499 except exceptions.DatabaseMalformed:
1500 pritext = _('Database Error')
1501 sectext = _('The database file (%s) cannot be read. Try to repair '
1502 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
1503 'it (all history will be lost).') % common.logger.LOG_DB_PATH
1504 self.dispatch('DB_ERROR', (pritext, sectext))
1505 self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
1506 tim, session))
1507
1529 recurs(args[pos], cid, bob_data)
1530 cb(*args)
1531 del self.awaiting_cids[cid]
1532 return
1533
1534
1535 for func in self.awaiting_cids[cid]:
1536 cb = func[0]
1537 args = func[1]
1538 cb(*args)
1539 del self.awaiting_cids[cid]
1540
1541 - def get_bob_data(self, cid, to, callback, args, position):
1542 """
1543 Request for BoB (XEP-0231) and when data will arrive, call callback
1544 with given args, after having replaced cid by it's data in
1545 args[position]
1546 """
1547 if cid in self.awaiting_cids:
1548 self.awaiting_cids[cid].appends((callback, args, position))
1549 else:
1550 self.awaiting_cids[cid] = [(callback, args, position)]
1551 iq = common.xmpp.Iq(to=to, typ='get')
1552 data = iq.addChild(name='data', attrs={'cid': cid},
1553 namespace=common.xmpp.NS_BOB)
1554 self.connection.SendAndCallForResponse(iq, self._on_bob_received,
1555 {'cid': cid})
1556
1557
1559 has_timestamp = bool(msg.timestamp)
1560
1561 subject = msg.getSubject()
1562
1563 if subject is not None:
1564 self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp))
1565 return
1566
1567 statusCode = msg.getStatusCode()
1568
1569 if not msg.getTag('body'):
1570
1571
1572 if msg.getTag('x'):
1573 if statusCode != []:
1574 self.dispatch('GC_CONFIG_CHANGE', (jid, statusCode))
1575 return
1576
1577 displaymarking = None
1578 seclabel = msg.getTag('securitylabel')
1579 if seclabel and seclabel.getNamespace() == common.xmpp.NS_SECLABEL:
1580 displaymarking = seclabel.getTag('displaymarking')
1581 if jid not in self.last_history_time:
1582 return
1583
1584 captcha = msg.getTag('captcha', namespace=common.xmpp.NS_CAPTCHA)
1585 if captcha:
1586 captcha = captcha.getTag('x', namespace=common.xmpp.NS_DATA)
1587 for field in captcha.getTags('field'):
1588 for media in field.getTags('media'):
1589 for uri in media.getTags('uri'):
1590 uri_data = uri.getData()
1591 if uri_data.startswith('cid:'):
1592 uri_data = uri_data[4:]
1593 found = False
1594 for data in msg.getTags('data',
1595 namespace=common.xmpp.NS_BOB):
1596 if data.getAttr('cid') == uri_data:
1597 uri.setData(data.getData())
1598 found = True
1599 if not found:
1600 self.get_bob_data(uri_data, frm,
1601 self.dispatch_gc_message, [msg, frm, msgtxt,
1602 jid, tim], 0)
1603 return
1604 self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp,
1605 msg.getXHTML(), statusCode, displaymarking, captcha))
1606
1607 tim_int = int(float(mktime(tim)))
1608 if gajim.config.should_log(self.name, jid) and not \
1609 tim_int <= self.last_history_time[jid] and msgtxt and frm.find('/') >= 0:
1610
1611
1612
1613 try:
1614 gajim.logger.write('gc_msg', frm, msgtxt, tim=tim)
1615
1616
1617
1618 self.last_history_time[jid] = mktime(tim)
1619
1620 except exceptions.PysqliteOperationalError, e:
1621 self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
1622 except exceptions.DatabaseMalformed:
1623 pritext = _('Database Error')
1624 sectext = _('The database file (%s) cannot be read. Try to repair '
1625 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
1626 'it (all history will be lost).') % common.logger.LOG_DB_PATH
1627 self.dispatch('DB_ERROR', (pritext, sectext))
1628
1645
1647 """
1648 Called when we receive a presence
1649 """
1650 gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received',
1651 conn=con, xmpp_pres=prs))
1652 ptype = prs.getType()
1653 if ptype == 'available':
1654 ptype = None
1655 rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed',
1656 'unsubscribe', 'unsubscribed')
1657 if ptype and not ptype in rfc_types:
1658 ptype = None
1659 log.debug('PresenceCB: %s' % ptype)
1660 if not self.connection or self.connected < 2:
1661 log.debug('account is no more connected')
1662 return
1663 try:
1664 who = helpers.get_full_jid_from_iq(prs)
1665 except Exception:
1666 if prs.getTag('error') and prs.getTag('error').getTag('jid-malformed'):
1667
1668
1669 who = str(prs.getFrom())
1670 jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
1671 self.dispatch('GC_MSG', (jid_stripped,
1672 _('Nickname not allowed: %s') % resource, None, False, None, []))
1673 return
1674 jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
1675 timestamp = None
1676 id_ = prs.getID()
1677 is_gc = False
1678 sigTag = None
1679 ns_muc_user_x = None
1680 avatar_sha = None
1681
1682 user_nick = prs.getTagData('nick')
1683 if not user_nick:
1684 user_nick = ''
1685 contact_nickname = None
1686 transport_auto_auth = False
1687
1688 delay_tag = prs.getTag('delay', namespace=common.xmpp.NS_DELAY2)
1689 if delay_tag:
1690 tim = prs.getTimestamp2()
1691 tim = helpers.datetime_tuple(tim)
1692 timestamp = localtime(timegm(tim))
1693 xtags = prs.getTags('x')
1694 for x in xtags:
1695 namespace = x.getNamespace()
1696 if namespace.startswith(common.xmpp.NS_MUC):
1697 is_gc = True
1698 if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'):
1699 ns_muc_user_x = x
1700 elif namespace == common.xmpp.NS_SIGNED:
1701 sigTag = x
1702 elif namespace == common.xmpp.NS_VCARD_UPDATE:
1703 avatar_sha = x.getTagData('photo')
1704 contact_nickname = x.getTagData('nickname')
1705 elif namespace == common.xmpp.NS_DELAY and not timestamp:
1706
1707 tim = prs.getTimestamp()
1708 tim = helpers.datetime_tuple(tim)
1709 timestamp = localtime(timegm(tim))
1710 elif namespace == 'http://delx.cjb.net/protocol/roster-subsync':
1711
1712 agent = gajim.get_server_from_jid(jid_stripped)
1713 if self.connection.getRoster().getItem(agent):
1714 transport_auto_auth = True
1715
1716 if not is_gc and id_ and id_.startswith('gajim_muc_') and \
1717 ptype == 'error':
1718
1719
1720 h = hmac.new(self.secret_hmac, jid_stripped).hexdigest()[:6]
1721 if id_.split('_')[-1] == h:
1722 is_gc = True
1723 status = prs.getStatus() or ''
1724 show = prs.getShow()
1725 if show not in ('chat', 'away', 'xa', 'dnd'):
1726 show = ''
1727 if not ptype and not show:
1728 show = 'online'
1729 elif ptype == 'unavailable':
1730 show = 'offline'
1731
1732 prio = prs.getPriority()
1733 try:
1734 prio = int(prio)
1735 except Exception:
1736 prio = 0
1737 keyID = ''
1738 if sigTag and self.USE_GPG and ptype != 'error':
1739
1740
1741 sigmsg = sigTag.getData()
1742 keyID = self.gpg.verify(status, sigmsg)
1743
1744 if is_gc:
1745 if ptype == 'error':
1746 errcon = prs.getError()
1747 errmsg = prs.getErrorMsg()
1748 errcode = prs.getErrorCode()
1749 room_jid, nick = gajim.get_room_and_nick_from_fjid(who)
1750
1751 gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
1752 self.name)
1753
1754
1755
1756
1757 if gc_control is None:
1758 minimized = gajim.interface.minimized_controls[self.name]
1759 gc_control = minimized.get(room_jid)
1760
1761 if errcode == '502':
1762
1763 self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource,
1764 prio, keyID, timestamp, None))
1765 elif (errcode == '503'):
1766 if gc_control is None or gc_control.autorejoin is None:
1767
1768 self.dispatch('GC_ERROR', (gc_control,
1769 _('Unable to join group chat'),
1770 _('Maximum number of users for %s has been '
1771 'reached') % room_jid))
1772 elif (errcode == '401') or (errcon == 'not-authorized'):
1773
1774 self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick))
1775 elif (errcode == '403') or (errcon == 'forbidden'):
1776
1777 self.dispatch('GC_ERROR', (gc_control,
1778 _('Unable to join group chat'),
1779 _('You are banned from group chat %s.') % room_jid))
1780 elif (errcode == '404') or (errcon in ('item-not-found',
1781 'remote-server-not-found')):
1782 if gc_control is None or gc_control.autorejoin is None:
1783
1784 self.dispatch('GC_ERROR', (gc_control,
1785 _('Unable to join group chat'),
1786 _('Group chat %s does not exist.') % room_jid))
1787 elif (errcode == '405') or (errcon == 'not-allowed'):
1788 self.dispatch('GC_ERROR', (gc_control,
1789 _('Unable to join group chat'),
1790 _('Group chat creation is restricted.')))
1791 elif (errcode == '406') or (errcon == 'not-acceptable'):
1792 self.dispatch('GC_ERROR', (gc_control,
1793 _('Unable to join group chat'),
1794 _('Your registered nickname must be used in group chat '
1795 '%s.') % room_jid))
1796 elif (errcode == '407') or (errcon == 'registration-required'):
1797 self.dispatch('GC_ERROR', (gc_control,
1798 _('Unable to join group chat'),
1799 _('You are not in the members list in groupchat %s.') %\
1800 room_jid))
1801 elif (errcode == '409') or (errcon == 'conflict'):
1802
1803 room_jid = gajim.get_room_from_fjid(who)
1804 self.dispatch('ASK_NEW_NICK', (room_jid,))
1805 else:
1806 self.dispatch('ERROR_ANSWER', ('', jid_stripped,
1807 errmsg, errcode))
1808 if not ptype or ptype == 'unavailable':
1809 if gajim.config.get('log_contact_status_changes') and \
1810 gajim.config.should_log(self.name, jid_stripped):
1811 gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped,
1812 resource)
1813 st = status or ''
1814 if gc_c:
1815 jid = gc_c.jid
1816 else:
1817 jid = prs.getJid()
1818 if jid:
1819
1820 st += ' (%s)' % jid
1821 try:
1822 gajim.logger.write('gcstatus', who, st, show)
1823 except exceptions.PysqliteOperationalError, e:
1824 self.dispatch('DB_ERROR', (_('Disk Write Error'),
1825 str(e)))
1826 except exceptions.DatabaseMalformed:
1827 pritext = _('Database Error')
1828 sectext = _('The database file (%s) cannot be read. Try to '
1829 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
1830 ' or remove it (all history will be lost).') % \
1831 common.logger.LOG_DB_PATH
1832 self.dispatch('DB_ERROR', (pritext, sectext))
1833 if avatar_sha or avatar_sha == '':
1834 if avatar_sha == '':
1835
1836 puny_nick = helpers.sanitize_filename(resource)
1837 gajim.interface.remove_avatar_files(jid_stripped, puny_nick)
1838
1839
1840 if ns_muc_user_x:
1841
1842
1843 reason = _('Room has been destroyed')
1844 destroy = ns_muc_user_x.getTag('destroy')
1845 r = destroy.getTagData('reason')
1846 if r:
1847 reason += ' (%s)' % r
1848 if destroy.getAttr('jid'):
1849 try:
1850 jid = helpers.parse_jid(destroy.getAttr('jid'))
1851 reason += '\n' + _('You can join this room instead: %s') \
1852 % jid
1853 except common.helpers.InvalidFormat:
1854 pass
1855 statusCode = ['destroyed']
1856 else:
1857 reason = prs.getReason()
1858 statusCode = prs.getStatusCode()
1859 self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource,
1860 prs.getRole(), prs.getAffiliation(), prs.getJid(),
1861 reason, prs.getActor(), statusCode, prs.getNewNick(),
1862 avatar_sha))
1863 return
1864
1865 if ptype == 'subscribe':
1866 log.debug('subscribe request from %s' % who)
1867 if who.find('@') <= 0 and who in self.agent_registrations:
1868 self.agent_registrations[who]['sub_received'] = True
1869 if not self.agent_registrations[who]['roster_push']:
1870
1871 return
1872 if gajim.config.get_per('accounts', self.name, 'autoauth') or \
1873 who.find('@') <= 0 or jid_stripped in self.jids_for_auto_auth or \
1874 transport_auto_auth:
1875 if self.connection:
1876 p = common.xmpp.Presence(who, 'subscribed')
1877 p = self.add_sha(p)
1878 self.connection.send(p)
1879 if who.find('@') <= 0 or transport_auto_auth:
1880 self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline',
1881 resource, prio, keyID, timestamp, None))
1882 if transport_auto_auth:
1883 self.automatically_added.append(jid_stripped)
1884 self.request_subscription(jid_stripped, name = user_nick)
1885 else:
1886 if not status:
1887 status = _('I would like to add you to my roster.')
1888 self.dispatch('SUBSCRIBE', (jid_stripped, status, user_nick))
1889 elif ptype == 'subscribed':
1890 if jid_stripped in self.automatically_added:
1891 self.automatically_added.remove(jid_stripped)
1892 else:
1893
1894 if jid_stripped not in self.subscribed_events:
1895 self.subscribed_events[jid_stripped] = []
1896 self.subscribed_events[jid_stripped].append(time_time())
1897 block = False
1898 if len(self.subscribed_events[jid_stripped]) > 5:
1899 if time_time() - self.subscribed_events[jid_stripped][0] < 5:
1900 block = True
1901 self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
1902 if block:
1903 gajim.config.set_per('account', self.name,
1904 'dont_ack_subscription', True)
1905 else:
1906 self.dispatch('SUBSCRIBED', (jid_stripped, resource))
1907
1908 log.debug(_('we are now subscribed to %s') % who)
1909 elif ptype == 'unsubscribe':
1910 log.debug(_('unsubscribe request from %s') % who)
1911 elif ptype == 'unsubscribed':
1912 log.debug(_('we are now unsubscribed from %s') % who)
1913
1914 if jid_stripped not in self.subscribed_events:
1915 self.subscribed_events[jid_stripped] = []
1916 self.subscribed_events[jid_stripped].append(time_time())
1917 block = False
1918 if len(self.subscribed_events[jid_stripped]) > 5:
1919 if time_time() - self.subscribed_events[jid_stripped][0] < 5:
1920 block = True
1921 self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
1922 if block:
1923 gajim.config.set_per('account', self.name, 'dont_ack_subscription',
1924 True)
1925 else:
1926 self.dispatch('UNSUBSCRIBED', jid_stripped)
1927 elif ptype == 'error':
1928 errmsg = prs.getError()
1929 errcode = prs.getErrorCode()
1930 if errcode != '502':
1931
1932 self.dispatch('ERROR_ANSWER', ('', jid_stripped,
1933 errmsg, errcode))
1934 if errcode != '409':
1935 self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource,
1936 prio, keyID, timestamp, None))
1937
1938 if ptype == 'unavailable':
1939 for jid in [jid_stripped, who]:
1940 if jid not in self.sessions:
1941 continue
1942
1943
1944 for sess in self.sessions[jid].values():
1945 if not sess.received_thread_id:
1946 contact = gajim.contacts.get_contact(self.name, jid)
1947
1948
1949
1950 contact_exists = bool(contact)
1951 session_supported = contact_exists and (
1952 contact.supports(common.xmpp.NS_SSN) or
1953 contact.supports(common.xmpp.NS_ESESSION))
1954 if session_supported:
1955 sess.terminate()
1956 del self.sessions[jid][sess.thread_id]
1957
1958 if avatar_sha is not None and ptype != 'error':
1959 if jid_stripped not in self.vcard_shas:
1960 cached_vcard = self.get_cached_vcard(jid_stripped)
1961 if cached_vcard and 'PHOTO' in cached_vcard and \
1962 'SHA' in cached_vcard['PHOTO']:
1963 self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA']
1964 else:
1965 self.vcard_shas[jid_stripped] = ''
1966 if avatar_sha != self.vcard_shas[jid_stripped]:
1967
1968 self.request_vcard(jid_stripped)
1969 if not ptype or ptype == 'unavailable':
1970 if gajim.config.get('log_contact_status_changes') and \
1971 gajim.config.should_log(self.name, jid_stripped):
1972 try:
1973 gajim.logger.write('status', jid_stripped, status, show)
1974 except exceptions.PysqliteOperationalError, e:
1975 self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
1976 except exceptions.DatabaseMalformed:
1977 pritext = _('Database Error')
1978 sectext = _('The database file (%s) cannot be read. Try to '
1979 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
1980 'or remove it (all history will be lost).') % \
1981 common.logger.LOG_DB_PATH
1982 self.dispatch('DB_ERROR', (pritext, sectext))
1983 our_jid = gajim.get_jid_from_account(self.name)
1984 if jid_stripped == our_jid and resource == self.server_resource:
1985
1986 self.dispatch('STATUS', show)
1987 else:
1988 self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio,
1989 keyID, timestamp, contact_nickname))
1990
1991
1994
2005
2030
2037
2045
2061
2071
2081
2083 roster_version = roster.version
2084 received_from_server = roster.received_from_server
2085 raw_roster = roster.getRaw()
2086 roster = {}
2087 our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
2088 if self.connected > 1 and self.continue_connect_info:
2089 msg = self.continue_connect_info[1]
2090 sign_msg = self.continue_connect_info[2]
2091 signed = ''
2092 send_first_presence = True
2093 if sign_msg:
2094 signed = self.get_signed_presence(msg, self._send_first_presence)
2095 if signed is None:
2096 self.dispatch('GPG_PASSWORD_REQUIRED',
2097 (self._send_first_presence,))
2098
2099 send_first_presence = False
2100 if send_first_presence:
2101 self._send_first_presence(signed)
2102
2103 for jid in raw_roster:
2104 try:
2105 j = helpers.parse_jid(jid)
2106 except Exception:
2107 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
2108 else:
2109 infos = raw_roster[jid]
2110 if jid != our_jid and (not infos['subscription'] or \
2111 infos['subscription'] == 'none') and (not infos['ask'] or \
2112 infos['ask'] == 'none') and not infos['name'] and \
2113 not infos['groups']:
2114
2115 self.connection.getRoster().delItem(jid)
2116 elif jid != our_jid:
2117 roster[j] = raw_roster[jid]
2118 if gajim.jid_is_transport(jid) and \
2119 not gajim.get_transport_name_from_jid(jid):
2120
2121 self.discoverInfo(jid)
2122
2123 gajim.logger.replace_roster(self.name, roster_version, roster)
2124 if received_from_server:
2125 for contact in gajim.contacts.iter_contacts(self.name):
2126 if not contact.is_groupchat() and contact.jid not in roster and \
2127 contact.jid != gajim.get_jid_from_account(self.name):
2128 self.dispatch('ROSTER_INFO', (contact.jid, None, None, None,
2129 ()))
2130 for jid in roster:
2131 self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'],
2132 roster[jid]['subscription'], roster[jid]['ask'],
2133 roster[jid]['groups']))
2134
2136 show = self.continue_connect_info[0]
2137 msg = self.continue_connect_info[1]
2138 sign_msg = self.continue_connect_info[2]
2139 if sign_msg and not signed:
2140 signed = self.get_signed_presence(msg)
2141 if signed is None:
2142 self.dispatch('BAD_PASSPHRASE', ())
2143 self.USE_GPG = False
2144 signed = ''
2145 self.connected = gajim.SHOW_LIST.index(show)
2146 sshow = helpers.get_xmpp_show(show)
2147
2148 if show == 'invisible':
2149 self.send_invisible_presence(msg, signed, True)
2150 return
2151 if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
2152 return
2153 priority = gajim.get_priority(self.name, sshow)
2154 our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
2155 vcard = self.get_cached_vcard(our_jid)
2156 if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']:
2157 self.vcard_sha = vcard['PHOTO']['SHA']
2158 p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
2159 p = self.add_sha(p)
2160 if msg:
2161 p.setStatus(msg)
2162 if signed:
2163 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
2164
2165 if self.connection:
2166 self.connection.send(p)
2167 self.priority = priority
2168 self.dispatch('STATUS', show)
2169 if self.vcard_supported:
2170
2171 self.request_vcard(None)
2172
2173
2174 self.get_bookmarks()
2175
2176
2177 self.get_annotations()
2178
2179
2180 self.dispatch('SIGNED_IN', ())
2181 self.send_awaiting_pep()
2182 self.continue_connect_info = None
2183
2205
2206
2208 jid = jid = helpers.get_jid_from_iq(iq_obj)
2209 tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH)
2210 if not tag:
2211 self.dispatch('SEARCH_FORM', (jid, None, False))
2212 return
2213 df = tag.getTag('x', namespace = common.xmpp.NS_DATA)
2214 if df:
2215 self.dispatch('SEARCH_FORM', (jid, df, True))
2216 return
2217 df = {}
2218 for i in iq_obj.getQueryPayload():
2219 df[i.getName()] = i.getData()
2220 self.dispatch('SEARCH_FORM', (jid, df, False))
2221
2226
2228
2229
2230 con.RegisterHandler('message', self._messageCB)
2231 con.RegisterHandler('presence', self._presenceCB)
2232 con.RegisterHandler('presence', self._capsPresenceCB)
2233
2234
2235
2236
2237 con.RegisterHandler('message', self._pubsubEventCB, makefirst=True)
2238 con.RegisterHandler('iq', self._vCardCB, 'result',
2239 common.xmpp.NS_VCARD)
2240 con.RegisterHandler('iq', self._rosterSetCB, 'set',
2241 common.xmpp.NS_ROSTER)
2242 con.RegisterHandler('iq', self._siSetCB, 'set',
2243 common.xmpp.NS_SI)
2244 con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set',
2245 common.xmpp.NS_ROSTERX)
2246 con.RegisterHandler('iq', self._siErrorCB, 'error',
2247 common.xmpp.NS_SI)
2248 con.RegisterHandler('iq', self._siResultCB, 'result',
2249 common.xmpp.NS_SI)
2250 con.RegisterHandler('iq', self._discoGetCB, 'get',
2251 common.xmpp.NS_DISCO)
2252 con.RegisterHandler('iq', self._bytestreamSetCB, 'set',
2253 common.xmpp.NS_BYTESTREAM)
2254 con.RegisterHandler('iq', self._bytestreamResultCB, 'result',
2255 common.xmpp.NS_BYTESTREAM)
2256 con.RegisterHandler('iq', self._bytestreamErrorCB, 'error',
2257 common.xmpp.NS_BYTESTREAM)
2258 con.RegisterHandler('iq', self._DiscoverItemsCB, 'result',
2259 common.xmpp.NS_DISCO_ITEMS)
2260 con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error',
2261 common.xmpp.NS_DISCO_ITEMS)
2262 con.RegisterHandler('iq', self._DiscoverInfoCB, 'result',
2263 common.xmpp.NS_DISCO_INFO)
2264 con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error',
2265 common.xmpp.NS_DISCO_INFO)
2266 con.RegisterHandler('iq', self._VersionCB, 'get',
2267 common.xmpp.NS_VERSION)
2268 con.RegisterHandler('iq', self._TimeCB, 'get',
2269 common.xmpp.NS_TIME)
2270 con.RegisterHandler('iq', self._TimeRevisedCB, 'get',
2271 common.xmpp.NS_TIME_REVISED)
2272 con.RegisterHandler('iq', self._LastCB, 'get',
2273 common.xmpp.NS_LAST)
2274 con.RegisterHandler('iq', self._LastResultCB, 'result',
2275 common.xmpp.NS_LAST)
2276 con.RegisterHandler('iq', self._VersionResultCB, 'result',
2277 common.xmpp.NS_VERSION)
2278 con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result',
2279 common.xmpp.NS_TIME_REVISED)
2280 con.RegisterHandler('iq', self._MucOwnerCB, 'result',
2281 common.xmpp.NS_MUC_OWNER)
2282 con.RegisterHandler('iq', self._MucAdminCB, 'result',
2283 common.xmpp.NS_MUC_ADMIN)
2284 con.RegisterHandler('iq', self._PrivateCB, 'result',
2285 common.xmpp.NS_PRIVATE)
2286 con.RegisterHandler('iq', self._SecLabelCB, 'result',
2287 common.xmpp.NS_SECLABEL_CATALOG)
2288 con.RegisterHandler('iq', self._HttpAuthCB, 'get',
2289 common.xmpp.NS_HTTP_AUTH)
2290 con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
2291 common.xmpp.NS_COMMANDS)
2292 con.RegisterHandler('iq', self._gMailNewMailCB, 'set',
2293 common.xmpp.NS_GMAILNOTIFY)
2294 con.RegisterHandler('iq', self._gMailQueryCB, 'result',
2295 common.xmpp.NS_GMAILNOTIFY)
2296 con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
2297 common.xmpp.NS_DISCO_INFO)
2298 con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
2299 common.xmpp.NS_DISCO_ITEMS)
2300 con.RegisterHandler('iq', self._IqPingCB, 'get',
2301 common.xmpp.NS_PING)
2302 con.RegisterHandler('iq', self._search_fields_received, 'result',
2303 common.xmpp.NS_SEARCH)
2304 con.RegisterHandler('iq', self._PrivacySetCB, 'set',
2305 common.xmpp.NS_PRIVACY)
2306 con.RegisterHandler('iq', self._PubSubCB, 'result')
2307 con.RegisterHandler('iq', self._PubSubErrorCB, 'error')
2308 con.RegisterHandler('iq', self._JingleCB, 'result')
2309 con.RegisterHandler('iq', self._JingleCB, 'error')
2310 con.RegisterHandler('iq', self._JingleCB, 'set',
2311 common.xmpp.NS_JINGLE)
2312 con.RegisterHandler('iq', self._ErrorCB, 'error')
2313 con.RegisterHandler('iq', self._IqCB)
2314 con.RegisterHandler('iq', self._StanzaArrivedCB)
2315 con.RegisterHandler('iq', self._ResultCB, 'result')
2316 con.RegisterHandler('presence', self._StanzaArrivedCB)
2317 con.RegisterHandler('message', self._StanzaArrivedCB)
2318 con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams')
2319
2328
2330 self.id_ = self.iq_obj.getID()
2331
2333 name = 'http-auth-received'
2334 base_network_events = []
2335
2337 if not self.conn:
2338 self.conn = self.base_event.conn
2339 if not self.iq_obj:
2340 self.iq_obj = self.base_event.xmpp_iq
2341
2342 self.opt = gajim.config.get_per('accounts', self.conn.name, 'http_auth')
2343 self.iq_id = self.iq_obj.getTagAttr('confirm', 'id')
2344 self.method = self.iq_obj.getTagAttr('confirm', 'method')
2345 self.url = self.iq_obj.getTagAttr('confirm', 'url')
2346
2347 self.msg = self.iq_obj.getTagData('body')
2348 return True
2349
2351 name = 'last-result-received'
2352 base_network_events = []
2353
2355 if not self.conn:
2356 self.conn = self.base_event.conn
2357 if not self.iq_obj:
2358 self.iq_obj = self.base_event.xmpp_iq
2359
2360 self.get_id()
2361 self.get_jid_resource()
2362 if self.id_ in self.conn.last_ids:
2363 self.conn.last_ids.remove(self.id_)
2364
2365 self.status = ''
2366 self.seconds = -1
2367
2368 if self.iq_obj.getType() == 'error':
2369 return True
2370
2371 qp = self.iq_obj.getTag('query')
2372 sec = qp.getAttr('seconds')
2373 self.status = qp.getData()
2374 try:
2375 self.seconds = int(sec)
2376 except Exception:
2377 return
2378
2379 return True
2380
2382 name = 'version-result-received'
2383 base_network_events = []
2384
2386 if not self.conn:
2387 self.conn = self.base_event.conn
2388 if not self.iq_obj:
2389 self.iq_obj = self.base_event.xmpp_iq
2390
2391 self.get_id()
2392 self.get_jid_resource()
2393 if self.id_ in self.conn.version_ids:
2394 self.conn.version_ids.remove(self.id_)
2395
2396 self.client_info = ''
2397 self.os_info = ''
2398
2399 if self.iq_obj.getType() == 'error':
2400 return True
2401
2402 qp = self.iq_obj.getTag('query')
2403 if qp.getTag('name'):
2404 self.client_info += qp.getTag('name').getData()
2405 if qp.getTag('version'):
2406 self.client_info += ' ' + qp.getTag('version').getData()
2407 if qp.getTag('os'):
2408 self.os_info += qp.getTag('os').getData()
2409
2410 return True
2411
2413 name = 'time-result-received'
2414 base_network_events = []
2415
2417 if not self.conn:
2418 self.conn = self.base_event.conn
2419 if not self.iq_obj:
2420 self.iq_obj = self.base_event.xmpp_iq
2421
2422 self.get_id()
2423 self.get_jid_resource()
2424 if self.id_ in self.conn.entity_time_ids:
2425 self.conn.entity_time_ids.remove(self.id_)
2426
2427 self.time_info = ''
2428
2429 if self.iq_obj.getType() == 'error':
2430 return True
2431
2432 qp = self.iq_obj.getTag('time')
2433 if not qp:
2434
2435 return
2436 tzo = qp.getTag('tzo').getData()
2437 if tzo.lower() == 'z':
2438 tzo = '0:0'
2439 tzoh, tzom = tzo.split(':')
2440 utc_time = qp.getTag('utc').getData()
2441 ZERO = datetime.timedelta(0)
2442 class UTC(datetime.tzinfo):
2443 def utcoffset(self, dt):
2444 return ZERO
2445 def tzname(self, dt):
2446 return "UTC"
2447 def dst(self, dt):
2448 return ZERO
2449
2450 class contact_tz(datetime.tzinfo):
2451 def utcoffset(self, dt):
2452 return datetime.timedelta(hours=int(tzoh), minutes=int(tzom))
2453 def tzname(self, dt):
2454 return "remote timezone"
2455 def dst(self, dt):
2456 return ZERO
2457
2458 try:
2459 t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ')
2460 t = t.replace(tzinfo=UTC())
2461 self.time_info = t.astimezone(contact_tz()).strftime('%c')
2462 except ValueError, e:
2463 log.info('Wrong time format: %s' % str(e))
2464 return
2465
2466 return True
2467
2469 name = 'gmail-notify'
2470 base_network_events = []
2471
2473 if not self.conn:
2474 self.conn = self.base_event.conn
2475 if not self.iq_obj:
2476 self.iq_obj = self.base_event.xmpp_iq
2477
2478 if not self.iq_obj.getTag('mailbox'):
2479 return
2480 mb = self.iq_obj.getTag('mailbox')
2481 if not mb.getAttr('url'):
2482 return
2483 self.conn.gmail_url = mb.getAttr('url')
2484 if mb.getNamespace() != common.xmpp.NS_GMAILNOTIFY:
2485 return
2486 self.newmsgs = mb.getAttr('total-matched')
2487 if not self.newmsgs:
2488 return
2489 if self.newmsgs == '0':
2490 return
2491
2492 self.gmail_messages_list = []
2493 if mb.getTag('mail-thread-info'):
2494 gmail_messages = mb.getTags('mail-thread-info')
2495 for gmessage in gmail_messages:
2496 unread_senders = []
2497 for sender in gmessage.getTag('senders').getTags(
2498 'sender'):
2499 if sender.getAttr('unread') != '1':
2500 continue
2501 if sender.getAttr('name'):
2502 unread_senders.append(sender.getAttr('name') + \
2503 '< ' + sender.getAttr('address') + '>')
2504 else:
2505 unread_senders.append(sender.getAttr('address'))
2506
2507 if not unread_senders:
2508 continue
2509 gmail_subject = gmessage.getTag('subject').getData()
2510 gmail_snippet = gmessage.getTag('snippet').getData()
2511 tid = int(gmessage.getAttr('tid'))
2512 if not self.conn.gmail_last_tid or \
2513 tid > self.conn.gmail_last_tid:
2514 self.conn.gmail_last_tid = tid
2515 self.gmail_messages_list.append({
2516 'From': unread_senders,
2517 'Subject': gmail_subject,
2518 'Snippet': gmail_snippet,
2519 'url': gmessage.getAttr('url'),
2520 'participation': gmessage.getAttr('participation'),
2521 'messages': gmessage.getAttr('messages'),
2522 'date': gmessage.getAttr('date')})
2523 self.conn.gmail_last_time = int(mb.getAttr('result-time'))
2524
2525 self.jid = gajim.get_jid_from_account(self.name)
2526 log.debug(('You have %s new gmail e-mails on %s.') % (self.newmsgs,
2527 self.jid))
2528 return True
2529
2531 name = 'roster-item-exchange-received'
2532 base_network_events = []
2533
2535 if not self.conn:
2536 self.conn = self.base_event.conn
2537 if not self.iq_obj:
2538 self.iq_obj = self.base_event.xmpp_iq
2539
2540 self.get_id()
2541 self.get_jid_resource()
2542 self.exchange_items_list = {}
2543 items_list = self.iq_obj.getTag('x').getChildren()
2544 if not items_list:
2545 return
2546 self.action = items_list[0].getAttr('action')
2547 if self.action is None:
2548 self.action = 'add'
2549 for item in self.iq_obj.getTag('x', namespace=common.xmpp.NS_ROSTERX).\
2550 getChildren():
2551 try:
2552 jid = helpers.parse_jid(item.getAttr('jid'))
2553 except common.helpers.InvalidFormat:
2554 log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
2555 continue
2556 name = item.getAttr('name')
2557 contact = gajim.contacts.get_contact(self.conn.name, jid)
2558 groups = []
2559 same_groups = True
2560 for group in item.getTags('group'):
2561 groups.append(group.getData())
2562
2563
2564 if not contact or group not in contact.groups:
2565 same_groups = False
2566 if contact:
2567
2568
2569 for group in contact.groups:
2570 if group not in groups:
2571 same_groups = False
2572 if contact.sub in ('both', 'to') and same_groups:
2573 continue
2574 self.exchange_items_list[jid] = []
2575 self.exchange_items_list[jid].append(name)
2576 self.exchange_items_list[jid].append(groups)
2577 if exchange_items_list:
2578 return True
2579