ability to see certificate information when fingerprint changes. see #3998
[gajim.git] / src / common / connection.py
blobbe74de6f64c3aa4e7f2f8fc128cfd1015289ac7d
1 # -*- coding:utf-8 -*-
2 ## src/common/connection.py
3 ##
4 ## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
5 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
6 ## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
7 ## Stéphan Kochen <stephan AT kochen.nl>
8 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
9 ## Travis Shirk <travis AT pobox.com>
10 ## Nikos Kouremenos <kourem AT gmail.com>
11 ## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
12 ## Stefan Bethge <stefan AT lanpartei.de>
13 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
14 ## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
15 ## Julien Pivotto <roidelapluie AT gmail.com>
16 ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
17 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
18 ## Jonathan Schleifer <js-gajim AT webkeks.org>
20 ## This file is part of Gajim.
22 ## Gajim is free software; you can redistribute it and/or modify
23 ## it under the terms of the GNU General Public License as published
24 ## by the Free Software Foundation; version 3 only.
26 ## Gajim is distributed in the hope that it will be useful,
27 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
28 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 ## GNU General Public License for more details.
31 ## You should have received a copy of the GNU General Public License
32 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
35 import os
36 import random
37 import socket
38 import operator
40 import time
41 import locale
42 import hmac
44 try:
45 randomsource = random.SystemRandom()
46 except Exception:
47 randomsource = random.Random()
48 randomsource.seed()
50 import signal
51 if os.name != 'nt':
52 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
54 import common.xmpp
55 from common import helpers
56 from common import gajim
57 from common import gpg
58 from common import passwords
59 from common import exceptions
61 from connection_handlers import *
63 from string import Template
64 import logging
65 log = logging.getLogger('gajim.c.connection')
67 ssl_error = {
68 2: _("Unable to get issuer certificate"),
69 3: _("Unable to get certificate CRL"),
70 4: _("Unable to decrypt certificate's signature"),
71 5: _("Unable to decrypt CRL's signature"),
72 6: _("Unable to decode issuer public key"),
73 7: _("Certificate signature failure"),
74 8: _("CRL signature failure"),
75 9: _("Certificate is not yet valid"),
76 10: _("Certificate has expired"),
77 11: _("CRL is not yet valid"),
78 12: _("CRL has expired"),
79 13: _("Format error in certificate's notBefore field"),
80 14: _("Format error in certificate's notAfter field"),
81 15: _("Format error in CRL's lastUpdate field"),
82 16: _("Format error in CRL's nextUpdate field"),
83 17: _("Out of memory"),
84 18: _("Self signed certificate"),
85 19: _("Self signed certificate in certificate chain"),
86 20: _("Unable to get local issuer certificate"),
87 21: _("Unable to verify the first certificate"),
88 22: _("Certificate chain too long"),
89 23: _("Certificate revoked"),
90 24: _("Invalid CA certificate"),
91 25: _("Path length constraint exceeded"),
92 26: _("Unsupported certificate purpose"),
93 27: _("Certificate not trusted"),
94 28: _("Certificate rejected"),
95 29: _("Subject issuer mismatch"),
96 30: _("Authority and subject key identifier mismatch"),
97 31: _("Authority and issuer serial number mismatch"),
98 32: _("Key usage does not include certificate signing"),
99 50: _("Application verification failure")
102 class CommonConnection:
104 Common connection class, can be derivated for normal connection or zeroconf
105 connection
108 def __init__(self, name):
109 self.name = name
110 # self.connected:
111 # 0=>offline,
112 # 1=>connection in progress,
113 # 2=>online
114 # 3=>free for chat
115 # ...
116 self.connected = 0
117 self.connection = None # xmpppy ClientCommon instance
118 self.on_purpose = False
119 self.is_zeroconf = False
120 self.password = ''
121 self.server_resource = self._compute_resource()
122 self.gpg = None
123 self.USE_GPG = False
124 if gajim.HAVE_GPG:
125 self.USE_GPG = True
126 self.gpg = gpg.GnuPG(gajim.config.get('use_gpg_agent'))
127 self.status = ''
128 self.old_show = ''
129 self.priority = gajim.get_priority(name, 'offline')
130 self.time_to_reconnect = None
131 self.bookmarks = []
133 self.blocked_list = []
134 self.blocked_contacts = []
135 self.blocked_groups = []
136 self.blocked_all = False
138 self.seclabel_supported = False
139 self.seclabel_catalogues = {}
141 self.pep_supported = False
142 self.pep = {}
143 # Do we continue connection when we get roster (send presence,get vcard..)
144 self.continue_connect_info = None
146 # Remember where we are in the register agent process
147 self.agent_registrations = {}
148 # To know the groupchat jid associated with a sranza ID. Useful to
149 # request vcard or os info... to a real JID but act as if it comes from
150 # the fake jid
151 self.groupchat_jids = {} # {ID : groupchat_jid}
153 self.privacy_rules_supported = False
154 self.vcard_supported = False
155 self.private_storage_supported = False
156 self.archiving_supported = False
157 self.archive_pref_supported = False
159 self.muc_jid = {} # jid of muc server for each transport type
160 self._stun_servers = [] # STUN servers of our jabber server
162 self.awaiting_cids = {} # Used for XEP-0231
164 self.nested_group_delimiter = '::'
166 self.get_config_values_or_default()
168 def _compute_resource(self):
169 resource = gajim.config.get_per('accounts', self.name, 'resource')
170 # All valid resource substitution strings should be added to this hash.
171 if resource:
172 resource = Template(resource).safe_substitute({
173 'hostname': socket.gethostname()
175 return resource
177 def dispatch(self, event, data):
179 Always passes account name as first param
181 gajim.ged.raise_event(event, self.name, data)
183 def _reconnect(self):
185 To be implemented by derivated classes
187 raise NotImplementedError
189 def quit(self, kill_core):
190 if kill_core and gajim.account_is_connected(self.name):
191 self.disconnect(on_purpose=True)
193 def test_gpg_passphrase(self, password):
195 Returns 'ok', 'bad_pass' or 'expired'
197 if not self.gpg:
198 return False
199 self.gpg.passphrase = password
200 keyID = gajim.config.get_per('accounts', self.name, 'keyid')
201 signed = self.gpg.sign('test', keyID)
202 self.gpg.password = None
203 if signed == 'KEYEXPIRED':
204 return 'expired'
205 elif signed == 'BAD_PASSPHRASE':
206 return 'bad_pass'
207 return 'ok'
209 def get_signed_msg(self, msg, callback = None):
211 Returns the signed message if possible or an empty string if gpg is not
212 used or None if waiting for passphrase
214 callback is the function to call when user give the passphrase
216 signed = ''
217 keyID = gajim.config.get_per('accounts', self.name, 'keyid')
218 if keyID and self.USE_GPG:
219 use_gpg_agent = gajim.config.get('use_gpg_agent')
220 if self.gpg.passphrase is None and not use_gpg_agent:
221 # We didn't set a passphrase
222 return None
223 signed = self.gpg.sign(msg, keyID)
224 if signed == 'BAD_PASSPHRASE':
225 self.USE_GPG = False
226 signed = ''
227 gajim.nec.push_incoming_event(BadGPGPassphraseEvent(None,
228 conn=self))
229 return signed
231 def _on_disconnected(self):
233 Called when a disconnect request has completed successfully
235 self.disconnect(on_purpose=True)
236 gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
237 show='offline'))
239 def get_status(self):
240 return gajim.SHOW_LIST[self.connected]
242 def check_jid(self, jid):
244 This function must be implemented by derivated classes. It has to return
245 the valid jid, or raise a helpers.InvalidFormat exception
247 raise NotImplementedError
249 def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
250 chatstate=None, msg_id=None, composing_xep=None, resource=None,
251 user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
252 label=None, original_message=None, delayed=None, callback=None):
253 if not self.connection or self.connected < 2:
254 return 1
255 try:
256 jid = self.check_jid(jid)
257 except helpers.InvalidFormat:
258 self.dispatch('ERROR', (_('Invalid Jabber ID'),
259 _('It is not possible to send a message to %s, this JID is not '
260 'valid.') % jid))
261 return
263 if msg and not xhtml and gajim.config.get(
264 'rst_formatting_outgoing_messages'):
265 from common.rst_xhtml_generator import create_xhtml
266 xhtml = create_xhtml(msg)
267 if not msg and chatstate is None and form_node is None:
268 return
269 fjid = jid
270 if resource:
271 fjid += '/' + resource
272 msgtxt = msg
273 msgenc = ''
275 if session:
276 fjid = session.get_to()
278 if keyID and self.USE_GPG:
279 xhtml = None
280 if keyID == 'UNKNOWN':
281 error = _('Neither the remote presence is signed, nor a key was '
282 'assigned.')
283 elif keyID.endswith('MISMATCH'):
284 error = _('The contact\'s key (%s) does not match the key assigned '
285 'in Gajim.' % keyID[:8])
286 else:
287 def encrypt_thread(msg, keyID, always_trust=False):
288 # encrypt message. This function returns (msgenc, error)
289 return self.gpg.encrypt(msg, [keyID], always_trust)
290 def _on_encrypted(output):
291 msgenc, error = output
292 if error == 'NOT_TRUSTED':
293 def _on_always_trust(answer):
294 if answer:
295 gajim.thread_interface(encrypt_thread, [msg, keyID,
296 True], _on_encrypted, [])
297 else:
298 self._message_encrypted_cb(output, type_, msg,
299 msgtxt, original_message, fjid, resource,
300 jid, xhtml, subject, chatstate, msg_id,
301 composing_xep, label, forward_from, delayed,
302 session, form_node, user_nick, keyID,
303 callback)
304 gajim.nec.push_incoming_event(GPGTrustKeyEvent(None,
305 conn=self, callback=_on_always_trust))
306 else:
307 self._message_encrypted_cb(output, type_, msg, msgtxt,
308 original_message, fjid, resource, jid, xhtml,
309 subject, chatstate, msg_id, composing_xep, label,
310 forward_from, delayed, session, form_node,
311 user_nick, keyID, callback)
312 gajim.thread_interface(encrypt_thread, [msg, keyID, False],
313 _on_encrypted, [])
314 return
316 self._message_encrypted_cb(('', error), type_, msg, msgtxt,
317 original_message, fjid, resource, jid, xhtml, subject,
318 chatstate, msg_id, composing_xep, label, forward_from, delayed,
319 session, form_node, user_nick, keyID, callback)
321 self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
322 resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
323 composing_xep, label, forward_from, delayed, session, form_node,
324 user_nick, callback)
326 def _message_encrypted_cb(self, output, type_, msg, msgtxt,
327 original_message, fjid, resource, jid, xhtml, subject, chatstate, msg_id,
328 composing_xep, label, forward_from, delayed, session, form_node, user_nick,
329 keyID, callback):
330 msgenc, error = output
332 if msgenc and not error:
333 msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
334 lang = os.getenv('LANG')
335 if lang is not None and lang != 'en': # we're not english
336 # one in locale and one en
337 msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
338 ' (' + msgtxt + ')'
339 self._on_continue_message(type_, msg, msgtxt, original_message,
340 fjid, resource, jid, xhtml, subject, msgenc, keyID,
341 chatstate, msg_id, composing_xep, label, forward_from, delayed,
342 session, form_node, user_nick, callback)
343 return
344 # Encryption failed, do not send message
345 tim = localtime()
346 gajim.nec.push_incoming_event(MessageNotSentEvent(None, conn=self,
347 jid=jid, message=msgtxt, error=error, time_=tim, session=session))
349 def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
350 resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
351 composing_xep, label, forward_from, delayed, session, form_node, user_nick,
352 callback):
353 if type_ == 'chat':
354 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
355 xhtml=xhtml)
356 else:
357 if subject:
358 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
359 subject=subject, xhtml=xhtml)
360 else:
361 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
362 xhtml=xhtml)
364 if msg_id:
365 msg_iq.setID(msg_id)
367 if msgenc:
368 msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
370 if form_node:
371 msg_iq.addChild(node=form_node)
372 if label:
373 msg_iq.addChild(node=label)
375 # XEP-0172: user_nickname
376 if user_nick:
377 msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
378 user_nick)
380 # TODO: We might want to write a function so we don't need to
381 # reproduce that ugly if somewhere else.
382 if resource:
383 contact = gajim.contacts.get_contact(self.name, jid, resource)
384 else:
385 contact = gajim.contacts.get_contact_with_highest_priority(self.name,
386 jid)
388 # chatstates - if peer supports xep85 or xep22, send chatstates
389 # please note that the only valid tag inside a message containing a <body>
390 # tag is the active event
391 if chatstate is not None and contact:
392 if ((composing_xep == 'XEP-0085' or not composing_xep) \
393 and composing_xep != 'asked_once') or \
394 contact.supports(common.xmpp.NS_CHATSTATES):
395 # XEP-0085
396 msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
397 if composing_xep in ('XEP-0022', 'asked_once') or \
398 not composing_xep:
399 # XEP-0022
400 chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT)
401 if chatstate is 'composing' or msgtxt:
402 chatstate_node.addChild(name='composing')
404 if forward_from:
405 addresses = msg_iq.addChild('addresses',
406 namespace=common.xmpp.NS_ADDRESS)
407 addresses.addChild('address', attrs = {'type': 'ofrom',
408 'jid': forward_from})
410 # XEP-0203
411 if delayed:
412 our_jid = gajim.get_jid_from_account(self.name) + '/' + \
413 self.server_resource
414 timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed))
415 msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2,
416 attrs={'from': our_jid, 'stamp': timestamp})
418 # XEP-0184
419 if msgtxt and gajim.config.get_per('accounts', self.name,
420 'request_receipt') and contact and contact.supports(
421 common.xmpp.NS_RECEIPTS):
422 msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
424 if session:
425 # XEP-0201
426 session.last_send = time.time()
427 msg_iq.setThread(session.thread_id)
429 # XEP-0200
430 if session.enable_encryption:
431 msg_iq = session.encrypt_stanza(msg_iq)
433 if callback:
434 callback(jid, msg, keyID, forward_from, session, original_message,
435 subject, type_, msg_iq)
437 def log_message(self, jid, msg, forward_from, session, original_message,
438 subject, type_):
439 if not forward_from and session and session.is_loggable():
440 ji = gajim.get_jid_without_resource(jid)
441 if gajim.config.should_log(self.name, ji):
442 log_msg = msg
443 if original_message is not None:
444 log_msg = original_message
445 if subject:
446 log_msg = _('Subject: %(subject)s\n%(message)s') % \
447 {'subject': subject, 'message': log_msg}
448 if log_msg:
449 if type_ == 'chat':
450 kind = 'chat_msg_sent'
451 else:
452 kind = 'single_msg_sent'
453 try:
454 gajim.logger.write(kind, jid, log_msg)
455 except exceptions.PysqliteOperationalError, e:
456 self.dispatch('DB_ERROR', (_('Disk Write Error'),
457 str(e)))
458 except exceptions.DatabaseMalformed:
459 pritext = _('Database Error')
460 sectext = _('The database file (%s) cannot be read. Try to '
461 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
462 ' or remove it (all history will be lost).') % \
463 common.logger.LOG_DB_PATH
464 self.dispatch('DB_ERROR', (pritext, sectext))
466 def ack_subscribed(self, jid):
468 To be implemented by derivated classes
470 raise NotImplementedError
472 def ack_unsubscribed(self, jid):
474 To be implemented by derivated classes
476 raise NotImplementedError
478 def request_subscription(self, jid, msg='', name='', groups=[],
479 auto_auth=False):
481 To be implemented by derivated classes
483 raise NotImplementedError
485 def send_authorization(self, jid):
487 To be implemented by derivated classes
489 raise NotImplementedError
491 def refuse_authorization(self, jid):
493 To be implemented by derivated classes
495 raise NotImplementedError
497 def unsubscribe(self, jid, remove_auth = True):
499 To be implemented by derivated classes
501 raise NotImplementedError
503 def unsubscribe_agent(self, agent):
505 To be implemented by derivated classes
507 raise NotImplementedError
509 def update_contact(self, jid, name, groups):
510 if self.connection:
511 self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
513 def update_contacts(self, contacts):
515 Update multiple roster items
517 if self.connection:
518 self.connection.getRoster().setItemMulti(contacts)
520 def new_account(self, name, config, sync=False):
522 To be implemented by derivated classes
524 raise NotImplementedError
526 def _on_new_account(self, con=None, con_type=None):
528 To be implemented by derivated classes
530 raise NotImplementedError
532 def account_changed(self, new_name):
533 self.name = new_name
535 def request_last_status_time(self, jid, resource, groupchat_jid=None):
537 groupchat_jid is used when we want to send a request to a real jid and
538 act as if the answer comes from the groupchat_jid
540 if not gajim.account_is_connected(self.name):
541 return
542 to_whom_jid = jid
543 if resource:
544 to_whom_jid += '/' + resource
545 iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\
546 common.xmpp.NS_LAST)
547 id_ = self.connection.getAnID()
548 iq.setID(id_)
549 if groupchat_jid:
550 self.groupchat_jids[id_] = groupchat_jid
551 self.last_ids.append(id_)
552 self.connection.send(iq)
554 def request_os_info(self, jid, resource):
556 To be implemented by derivated classes
558 raise NotImplementedError
560 def get_settings(self):
562 To be implemented by derivated classes
564 raise NotImplementedError
566 def get_bookmarks(self):
568 To be implemented by derivated classes
570 raise NotImplementedError
572 def store_bookmarks(self):
574 To be implemented by derivated classes
576 raise NotImplementedError
578 def get_metacontacts(self):
580 To be implemented by derivated classes
582 raise NotImplementedError
584 def send_agent_status(self, agent, ptype):
586 To be implemented by derivated classes
588 raise NotImplementedError
590 def gpg_passphrase(self, passphrase):
591 if self.gpg:
592 use_gpg_agent = gajim.config.get('use_gpg_agent')
593 if use_gpg_agent:
594 self.gpg.passphrase = None
595 else:
596 self.gpg.passphrase = passphrase
598 def ask_gpg_keys(self):
599 if self.gpg:
600 return self.gpg.get_keys()
601 return None
603 def ask_gpg_secrete_keys(self):
604 if self.gpg:
605 return self.gpg.get_secret_keys()
606 return None
608 def load_roster_from_db(self):
609 # Do nothing by default
610 return
612 def _event_dispatcher(self, realm, event, data):
613 if realm == '':
614 if event == common.xmpp.transports_nb.DATA_RECEIVED:
615 gajim.nec.push_incoming_event(StanzaReceivedEvent(None,
616 conn=self, stanza_str=unicode(data, errors='ignore')))
617 elif event == common.xmpp.transports_nb.DATA_SENT:
618 gajim.nec.push_incoming_event(StanzaSentEvent(None, conn=self,
619 stanza_str=unicode(data)))
621 def change_status(self, show, msg, auto=False):
622 if not msg:
623 msg = ''
624 sign_msg = False
625 if not auto and not show == 'offline':
626 sign_msg = True
627 if show != 'invisible':
628 # We save it only when privacy list is accepted
629 self.status = msg
630 if show != 'offline' and self.connected < 1:
631 # set old_show to requested 'show' in case we need to
632 # recconect before we auth to server
633 self.old_show = show
634 self.on_purpose = False
635 self.server_resource = self._compute_resource()
636 if gajim.HAVE_GPG:
637 self.USE_GPG = True
638 self.gpg = gpg.GnuPG(gajim.config.get('use_gpg_agent'))
639 self.connect_and_init(show, msg, sign_msg)
640 return
642 if show == 'offline':
643 self.connected = 0
644 if self.connection:
645 p = common.xmpp.Presence(typ = 'unavailable')
646 p = self.add_sha(p, False)
647 if msg:
648 p.setStatus(msg)
650 self.connection.RegisterDisconnectHandler(self._on_disconnected)
651 self.connection.send(p, now=True)
652 self.connection.start_disconnect()
653 else:
654 self._on_disconnected()
655 return
657 if show != 'offline' and self.connected > 0:
658 # dont'try to connect, when we are in state 'connecting'
659 if self.connected == 1:
660 return
661 if show == 'invisible':
662 self._change_to_invisible(msg)
663 return
664 if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
665 return -1
666 was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
667 self.connected = gajim.SHOW_LIST.index(show)
668 if was_invisible:
669 self._change_from_invisible()
670 self._update_status(show, msg)
672 class Connection(CommonConnection, ConnectionHandlers):
673 def __init__(self, name):
674 CommonConnection.__init__(self, name)
675 ConnectionHandlers.__init__(self)
676 # this property is used to prevent double connections
677 self.last_connection = None # last ClientCommon instance
678 # If we succeed to connect, remember it so next time we try (after a
679 # disconnection) we try only this type.
680 self.last_connection_type = None
681 self.lang = None
682 if locale.getdefaultlocale()[0]:
683 self.lang = locale.getdefaultlocale()[0].split('_')[0]
684 # increase/decrease default timeout for server responses
685 self.try_connecting_for_foo_secs = 45
686 # holds the actual hostname to which we are connected
687 self.connected_hostname = None
688 self.last_time_to_reconnect = None
689 self.new_account_info = None
690 self.new_account_form = None
691 self.annotations = {}
692 self.last_io = gajim.idlequeue.current_time()
693 self.last_sent = []
694 self.last_history_time = {}
695 self.password = passwords.get_password(name)
697 self.music_track_info = 0
698 self.location_info = {}
699 self.pubsub_supported = False
700 self.pubsub_publish_options_supported = False
701 # Do we auto accept insecure connection
702 self.connection_auto_accepted = False
703 self.pasword_callback = None
705 self.on_connect_success = None
706 self.on_connect_failure = None
707 self.retrycount = 0
708 self.jids_for_auto_auth = [] # list of jid to auto-authorize
709 self.available_transports = {} # list of available transports on this
710 # server {'icq': ['icq.server.com', 'icq2.server.com'], }
711 self.private_storage_supported = True
712 self.streamError = ''
713 self.secret_hmac = str(random.random())[2:]
714 gajim.ged.register_event_handler('privacy-list-received', ged.CORE,
715 self._nec_privacy_list_received)
716 gajim.ged.register_event_handler('agent-info-error-received', ged.CORE,
717 self._nec_agent_info_error_received)
718 gajim.ged.register_event_handler('agent-info-received', ged.CORE,
719 self._nec_agent_info_received)
720 # END __init__
722 def cleanup(self):
723 ConnectionHandlers.cleanup(self)
724 gajim.ged.remove_event_handler('privacy-list-received', ged.CORE,
725 self._nec_privacy_list_received)
726 gajim.ged.remove_event_handler('agent-info-error-received', ged.CORE,
727 self._nec_agent_info_error_received)
728 gajim.ged.remove_event_handler('agent-info-received', ged.CORE,
729 self._nec_agent_info_received)
731 def get_config_values_or_default(self):
732 if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
733 self.keepalives = gajim.config.get_per('accounts', self.name,
734 'keep_alive_every_foo_secs')
735 else:
736 self.keepalives = 0
737 if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'):
738 self.pingalives = gajim.config.get_per('accounts', self.name,
739 'ping_alive_every_foo_secs')
740 else:
741 self.pingalives = 0
742 self.client_cert = gajim.config.get_per('accounts', self.name,
743 'client_cert')
745 def check_jid(self, jid):
746 return helpers.parse_jid(jid)
748 def _reconnect(self):
749 # Do not try to reco while we are already trying
750 self.time_to_reconnect = None
751 if self.connected < 2: # connection failed
752 log.debug('reconnect')
753 self.connected = 1
754 gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
755 show='connecting'))
756 self.retrycount += 1
757 self.on_connect_auth = self._discover_server_at_connection
758 self.connect_and_init(self.old_show, self.status, self.USE_GPG)
759 else:
760 # reconnect succeeded
761 self.time_to_reconnect = None
762 self.retrycount = 0
764 # We are doing disconnect at so many places, better use one function in all
765 def disconnect(self, on_purpose=False):
766 gajim.interface.music_track_changed(None, None, self.name)
767 self.reset_awaiting_pep()
768 self.on_purpose = on_purpose
769 self.connected = 0
770 self.time_to_reconnect = None
771 self.privacy_rules_supported = False
772 if self.connection:
773 # make sure previous connection is completely closed
774 gajim.proxy65_manager.disconnect(self.connection)
775 self.terminate_sessions()
776 self.remove_all_transfers()
777 self.connection.disconnect()
778 self.last_connection = None
779 self.connection = None
781 def _disconnectedReconnCB(self):
783 Called when we are disconnected
785 log.info('disconnectedReconnCB called')
786 if gajim.account_is_connected(self.name):
787 # we cannot change our status to offline or connecting
788 # after we auth to server
789 self.old_show = gajim.SHOW_LIST[self.connected]
790 self.connected = 0
791 if not self.on_purpose:
792 gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
793 show='offline'))
794 self.disconnect()
795 if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
796 self.connected = -1
797 gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
798 show='error'))
799 if gajim.status_before_autoaway[self.name]:
800 # We were auto away. So go back online
801 self.status = gajim.status_before_autoaway[self.name]
802 gajim.status_before_autoaway[self.name] = ''
803 self.old_show = 'online'
804 # this check has moved from _reconnect method
805 # do exponential backoff until 15 minutes,
806 # then small linear increase
807 if self.retrycount < 2 or self.last_time_to_reconnect is None:
808 self.last_time_to_reconnect = 5
809 if self.last_time_to_reconnect < 800:
810 self.last_time_to_reconnect *= 1.5
811 self.last_time_to_reconnect += randomsource.randint(0, 5)
812 self.time_to_reconnect = int(self.last_time_to_reconnect)
813 log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect)
814 gajim.idlequeue.set_alarm(self._reconnect_alarm,
815 self.time_to_reconnect)
816 elif self.on_connect_failure:
817 self.on_connect_failure()
818 self.on_connect_failure = None
819 else:
820 # show error dialog
821 self._connection_lost()
822 else:
823 self.disconnect()
824 self.on_purpose = False
825 # END disconnectedReconnCB
827 def _connection_lost(self):
828 log.debug('_connection_lost')
829 self.disconnect(on_purpose = False)
830 gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
831 title=_('Connection with account "%s" has been lost') % self.name,
832 msg=_('Reconnect manually.')))
834 def _event_dispatcher(self, realm, event, data):
835 CommonConnection._event_dispatcher(self, realm, event, data)
836 if realm == common.xmpp.NS_REGISTER:
837 if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:
838 # data is (agent, DataFrom, is_form, error_msg)
839 if self.new_account_info and \
840 self.new_account_info['hostname'] == data[0]:
841 # it's a new account
842 if not data[1]: # wrong answer
843 reason = _('Server %(name)s answered wrongly to '
844 'register request: %(error)s') % {'name': data[0],
845 'error': data[3]}
846 gajim.nec.push_incoming_event(AccountNotCreatedEvent(
847 None, conn=self, reason=reason))
848 return
849 is_form = data[2]
850 conf = data[1]
851 if data[4] is not '':
852 helpers.replace_dataform_media(conf, data[4])
853 if self.new_account_form:
854 def _on_register_result(result):
855 if not common.xmpp.isResultNode(result):
856 gajim.nec.push_incoming_event(AccountNotCreatedEvent(
857 None, conn=self, reason=result.getError()))
858 return
859 if gajim.HAVE_GPG:
860 self.USE_GPG = True
861 self.gpg = gpg.GnuPG(gajim.config.get(
862 'use_gpg_agent'))
863 gajim.nec.push_incoming_event(
864 AccountCreatedEvent(None, conn=self,
865 account_info = self.new_account_info))
866 self.new_account_info = None
867 self.new_account_form = None
868 if self.connection:
869 self.connection.UnregisterDisconnectHandler(
870 self._on_new_account)
871 self.disconnect(on_purpose=True)
872 # it's the second time we get the form, we have info user
873 # typed, so send them
874 if is_form:
875 #TODO: Check if form has changed
876 iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER,
877 to=self._hostname)
878 iq.setTag('query').addChild(node=self.new_account_form)
879 self.connection.SendAndCallForResponse(iq,
880 _on_register_result)
881 else:
882 if self.new_account_form.keys().sort() != \
883 conf.keys().sort():
884 # requested config has changed since first connection
885 reason = _('Server %s provided a different '
886 'registration form') % data[0]
887 gajim.nec.push_incoming_event(AccountNotCreatedEvent(
888 None, conn=self, reason=reason))
889 return
890 common.xmpp.features_nb.register(self.connection,
891 self._hostname, self.new_account_form,
892 _on_register_result)
893 return
894 gajim.nec.push_incoming_event(NewAccountConnectedEvent(None,
895 conn=self, config=conf, is_form=is_form))
896 self.connection.UnregisterDisconnectHandler(
897 self._on_new_account)
898 self.disconnect(on_purpose=True)
899 return
900 if not data[1]: # wrong answer
901 self.dispatch('ERROR', (_('Invalid answer'),
902 _('Transport %(name)s answered wrongly to register '
903 'request: %(error)s') % {'name': data[0],
904 'error': data[3]}))
905 return
906 is_form = data[2]
907 conf = data[1]
908 gajim.nec.push_incoming_event(RegisterAgentInfoReceivedEvent(
909 None, conn=self, agent=data[0], config=conf,
910 is_form=is_form))
911 elif realm == common.xmpp.NS_PRIVACY:
912 if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED:
913 # data is (list)
914 gajim.nec.push_incoming_event(PrivacyListsReceivedEvent(None,
915 conn=self, lists_list=data))
916 elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED:
917 # data is (resp)
918 if not data:
919 return
920 rules = []
921 name = data.getTag('query').getTag('list').getAttr('name')
922 for child in data.getTag('query').getTag('list').getChildren():
923 dict_item = child.getAttrs()
924 childs = []
925 if 'type' in dict_item:
926 for scnd_child in child.getChildren():
927 childs += [scnd_child.getName()]
928 rules.append({'action':dict_item['action'],
929 'type':dict_item['type'], 'order':dict_item['order'],
930 'value':dict_item['value'], 'child':childs})
931 else:
932 for scnd_child in child.getChildren():
933 childs.append(scnd_child.getName())
934 rules.append({'action':dict_item['action'],
935 'order':dict_item['order'], 'child':childs})
936 gajim.nec.push_incoming_event(PrivacyListReceivedEvent(None,
937 conn=self, list_name=name, rules=rules))
938 elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
939 # data is (dict)
940 gajim.nec.push_incoming_event(PrivacyListActiveDefaultEvent(
941 None, conn=self, active_list=data['active'],
942 default_list=data['default']))
944 def _select_next_host(self, hosts):
946 Selects the next host according to RFC2782 p.3 based on it's priority.
947 Chooses between hosts with the same priority randomly, where the
948 probability of being selected is proportional to the weight of the host
950 hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio'))
952 try:
953 lowest_prio = hosts_by_prio[0]['prio']
954 except IndexError:
955 raise ValueError("No hosts to choose from!")
957 hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio]
959 if len(hosts_lowest_prio) == 1:
960 return hosts_lowest_prio[0]
961 else:
962 rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio))
963 weightsum = 0
964 for host in sorted(hosts_lowest_prio, key=operator.itemgetter(
965 'weight')):
966 weightsum += host['weight']
967 if weightsum >= rndint:
968 return host
970 def connect(self, data = None):
972 Start a connection to the Jabber server
974 Returns connection, and connection type ('tls', 'ssl', 'plain', '') data
975 MUST contain hostname, usessl, proxy, use_custom_host, custom_host (if
976 use_custom_host), custom_port (if use_custom_host)
978 if self.connection:
979 return self.connection, ''
981 if data:
982 hostname = data['hostname']
983 self.try_connecting_for_foo_secs = 45
984 p = data['proxy']
985 use_srv = True
986 use_custom = data['use_custom_host']
987 if use_custom:
988 custom_h = data['custom_host']
989 custom_p = data['custom_port']
990 else:
991 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
992 usessl = gajim.config.get_per('accounts', self.name, 'usessl')
993 self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
994 self.name, 'try_connecting_for_foo_secs')
995 p = gajim.config.get_per('accounts', self.name, 'proxy')
996 use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
997 use_custom = gajim.config.get_per('accounts', self.name,
998 'use_custom_host')
999 custom_h = gajim.config.get_per('accounts', self.name, 'custom_host')
1000 custom_p = gajim.config.get_per('accounts', self.name, 'custom_port')
1002 # create connection if it doesn't already exist
1003 self.connected = 1
1004 if p and p in gajim.config.get_per('proxies'):
1005 proxy = {}
1006 proxyptr = gajim.config.get_per('proxies', p)
1007 for key in proxyptr.keys():
1008 proxy[key] = proxyptr[key][1]
1010 elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
1011 try:
1012 try:
1013 env_http_proxy = os.environ['HTTP_PROXY']
1014 except Exception:
1015 env_http_proxy = os.environ['http_proxy']
1016 env_http_proxy = env_http_proxy.strip('"')
1017 # Dispose of the http:// prefix
1018 env_http_proxy = env_http_proxy.split('://')
1019 env_http_proxy = env_http_proxy[len(env_http_proxy)-1]
1020 env_http_proxy = env_http_proxy.split('@')
1022 if len(env_http_proxy) == 2:
1023 login = env_http_proxy[0].split(':')
1024 addr = env_http_proxy[1].split(':')
1025 else:
1026 login = ['', '']
1027 addr = env_http_proxy[0].split(':')
1029 proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]}
1031 if len(addr) == 2:
1032 proxy['port'] = addr[1]
1033 else:
1034 proxy['port'] = 3128
1036 if len(login) == 2:
1037 proxy['pass'] = login[1]
1038 proxy['useauth'] = True
1039 else:
1040 proxy['pass'] = u''
1042 except Exception:
1043 proxy = None
1044 else:
1045 proxy = None
1046 h = hostname
1047 p = 5222
1048 ssl_p = 5223
1049 # use_srv = False # wants ssl? disable srv lookup
1050 if use_custom:
1051 h = custom_h
1052 p = custom_p
1053 ssl_p = custom_p
1054 use_srv = False
1056 # SRV resolver
1057 self._proxy = proxy
1058 self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
1059 'weight': 10} ]
1060 self._hostname = hostname
1061 if use_srv:
1062 # add request for srv query to the resolve, on result '_on_resolve'
1063 # will be called
1064 gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h),
1065 self._on_resolve)
1066 else:
1067 self._on_resolve('', [])
1069 def _on_resolve(self, host, result_array):
1070 # SRV query returned at least one valid result, we put it in hosts dict
1071 if len(result_array) != 0:
1072 self._hosts = [i for i in result_array]
1073 # Add ssl port
1074 ssl_p = 5223
1075 if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
1076 ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port')
1077 for i in self._hosts:
1078 i['ssl_port'] = ssl_p
1079 self._connect_to_next_host()
1082 def _connect_to_next_host(self, retry=False):
1083 log.debug('Connection to next host')
1084 if len(self._hosts):
1085 # No config option exist when creating a new account
1086 if self.last_connection_type:
1087 self._connection_types = [self.last_connection_type]
1088 elif self.name in gajim.config.get_per('accounts'):
1089 self._connection_types = gajim.config.get_per('accounts', self.name,
1090 'connection_types').split()
1091 else:
1092 self._connection_types = ['tls', 'ssl', 'plain']
1094 if self._proxy and self._proxy['type']=='bosh':
1095 # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain"
1096 # connection and TLS with handshake right after TCP connecting ("ssl")
1097 scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
1098 if scheme=='https':
1099 self._connection_types = ['ssl']
1100 else:
1101 self._connection_types = ['plain']
1103 host = self._select_next_host(self._hosts)
1104 self._current_host = host
1105 self._hosts.remove(host)
1106 self.connect_to_next_type()
1108 else:
1109 if not retry and self.retrycount == 0:
1110 log.debug("Out of hosts, giving up connecting to %s", self.name)
1111 self.time_to_reconnect = None
1112 if self.on_connect_failure:
1113 self.on_connect_failure()
1114 self.on_connect_failure = None
1115 else:
1116 # shown error dialog
1117 self._connection_lost()
1118 else:
1119 # try reconnect if connection has failed before auth to server
1120 self._disconnectedReconnCB()
1122 def connect_to_next_type(self, retry=False):
1123 if len(self._connection_types):
1124 self._current_type = self._connection_types.pop(0)
1125 if self.last_connection:
1126 self.last_connection.socket.disconnect()
1127 self.last_connection = None
1128 self.connection = None
1130 if self._current_type == 'ssl':
1131 # SSL (force TLS on different port than plain)
1132 # If we do TLS over BOSH, port of XMPP server should be the standard one
1133 # and TLS should be negotiated because TLS on 5223 is deprecated
1134 if self._proxy and self._proxy['type']=='bosh':
1135 port = self._current_host['port']
1136 else:
1137 port = self._current_host['ssl_port']
1138 elif self._current_type == 'tls':
1139 # TLS - negotiate tls after XMPP stream is estabilished
1140 port = self._current_host['port']
1141 elif self._current_type == 'plain':
1142 # plain connection on defined port
1143 port = self._current_host['port']
1145 cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
1146 mycerts = common.gajim.MY_CACERTS
1147 secure_tuple = (self._current_type, cacerts, mycerts)
1149 con = common.xmpp.NonBlockingClient(
1150 domain=self._hostname,
1151 caller=self,
1152 idlequeue=gajim.idlequeue)
1154 self.last_connection = con
1155 # increase default timeout for server responses
1156 common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
1157 # FIXME: this is a hack; need a better way
1158 if self.on_connect_success == self._on_new_account:
1159 con.RegisterDisconnectHandler(self._on_new_account)
1161 self.log_hosttype_info(port)
1162 con.connect(
1163 hostname=self._current_host['host'],
1164 port=port,
1165 on_connect=self.on_connect_success,
1166 on_proxy_failure=self.on_proxy_failure,
1167 on_connect_failure=self.connect_to_next_type,
1168 proxy=self._proxy,
1169 secure_tuple = secure_tuple)
1170 else:
1171 self._connect_to_next_host(retry)
1173 def log_hosttype_info(self, port):
1174 msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
1175 self._current_host['host'], port, self._current_type)
1176 log.info(msg)
1177 if self._proxy:
1178 msg = '>>>>>> '
1179 if self._proxy['type']=='bosh':
1180 msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri'])
1181 if self._proxy['type'] in ['http', 'socks5'] or self._proxy['bosh_useproxy']:
1182 msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port'])
1183 log.info(msg)
1185 def _connect_failure(self, con_type=None):
1186 if not con_type:
1187 # we are not retrying, and not conecting
1188 if not self.retrycount and self.connected != 0:
1189 self.disconnect(on_purpose = True)
1190 pritxt = _('Could not connect to "%s"') % self._hostname
1191 sectxt = _('Check your connection or try again later.')
1192 if self.streamError:
1193 # show error dialog
1194 key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError
1195 if key in common.xmpp.ERRORS:
1196 sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2]
1197 self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt)))
1198 return
1199 # show popup
1200 gajim.nec.push_incoming_event(ConnectionLostEvent(None,
1201 conn=self, title=pritxt, msg=sectxt))
1203 def on_proxy_failure(self, reason):
1204 log.error('Connection to proxy failed: %s' % reason)
1205 self.time_to_reconnect = None
1206 self.on_connect_failure = None
1207 self.disconnect(on_purpose = True)
1208 gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
1209 title=_('Connection to proxy failed'), msg=reason))
1211 def _connect_success(self, con, con_type):
1212 if not self.connected: # We went offline during connecting process
1213 # FIXME - not possible, maybe it was when we used threads
1214 return
1215 _con_type = con_type
1216 if _con_type != self._current_type:
1217 log.info('Connecting to next type beacuse desired is %s and returned is %s'
1218 % (self._current_type, _con_type))
1219 self.connect_to_next_type()
1220 return
1221 con.RegisterDisconnectHandler(self._on_disconnected)
1222 if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
1223 'warn_when_plaintext_connection'):
1224 gajim.nec.push_incoming_event(PlainConnectionEvent(None, conn=self,
1225 xmpp_client=con))
1226 return True
1227 if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \
1228 and gajim.config.get_per('accounts', self.name,
1229 'warn_when_insecure_ssl_connection') and \
1230 not self.connection_auto_accepted:
1231 # Pyopenssl is not used
1232 gajim.nec.push_incoming_event(InsecureSSLConnectionEvent(None,
1233 conn=self, xmpp_client=con, conn_type=_con_type))
1234 return True
1235 return self.connection_accepted(con, con_type)
1237 def connection_accepted(self, con, con_type):
1238 if not con or not con.Connection:
1239 self.disconnect(on_purpose=True)
1240 gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
1241 title=_('Could not connect to account %s') % self.name,
1242 msg=_('Connection with account %s has been lost. Retry '
1243 'connecting.') % self.name))
1244 return
1245 self.hosts = []
1246 self.connection_auto_accepted = False
1247 self.connected_hostname = self._current_host['host']
1248 self.on_connect_failure = None
1249 con.UnregisterDisconnectHandler(self._on_disconnected)
1250 con.RegisterDisconnectHandler(self._disconnectedReconnCB)
1251 log.debug('Connected to server %s:%s with %s' % (
1252 self._current_host['host'], self._current_host['port'], con_type))
1254 self.last_connection_type = con_type
1255 if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
1256 name = None
1257 else:
1258 name = gajim.config.get_per('accounts', self.name, 'name')
1259 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
1260 self.connection = con
1261 try:
1262 errnum = con.Connection.ssl_errnum
1263 except AttributeError:
1264 errnum = -1 # we don't have an errnum
1265 if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts',
1266 self.name, 'ignore_ssl_errors'):
1267 text = _('The authenticity of the %s certificate could be invalid.') %\
1268 hostname
1269 if errnum in ssl_error:
1270 text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
1271 else:
1272 text += _('\nUnknown SSL error: %d') % errnum
1273 gajim.nec.push_incoming_event(SSLErrorEvent(None, conn=self,
1274 error_text=text, error_num=errnum,
1275 cert=con.Connection.ssl_cert_pem,
1276 fingerprint=con.Connection.ssl_fingerprint_sha1))
1277 return True
1278 if hasattr(con.Connection, 'ssl_fingerprint_sha1'):
1279 saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1')
1280 if saved_fingerprint:
1281 # Check sha1 fingerprint
1282 if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint:
1283 gajim.nec.push_incoming_event(FingerprintErrorEvent(None,
1284 conn=self, certificate=con.Connection.ssl_certificate,
1285 new_fingerprint=con.Connection.ssl_fingerprint_sha1))
1286 return True
1287 else:
1288 gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1',
1289 con.Connection.ssl_fingerprint_sha1)
1290 self._register_handlers(con, con_type)
1291 con.auth(
1292 user=name,
1293 password=self.password,
1294 resource=self.server_resource,
1295 sasl=1,
1296 on_auth=self.__on_auth)
1298 def ssl_certificate_accepted(self):
1299 if not self.connection:
1300 self.disconnect(on_purpose=True)
1301 gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
1302 title=_('Could not connect to account %s') % self.name,
1303 msg=_('Connection with account %s has been lost. Retry '
1304 'connecting.') % self.name))
1305 return
1306 name = gajim.config.get_per('accounts', self.name, 'name')
1307 self._register_handlers(self.connection, 'ssl')
1308 self.connection.auth(name, self.password, self.server_resource, 1,
1309 self.__on_auth)
1311 def _register_handlers(self, con, con_type):
1312 self.peerhost = con.get_peerhost()
1313 gajim.con_types[self.name] = con_type
1314 # notify the gui about con_type
1315 gajim.nec.push_incoming_event(ConnectionTypeEvent(None,
1316 conn=self, connection_type=con_type))
1317 ConnectionHandlers._register_handlers(self, con, con_type)
1319 def __on_auth(self, con, auth):
1320 if not con:
1321 self.disconnect(on_purpose=True)
1322 gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
1323 title=_('Could not connect to "%s"') % self._hostname,
1324 msg=_('Check your connection or try again later')))
1325 if self.on_connect_auth:
1326 self.on_connect_auth(None)
1327 self.on_connect_auth = None
1328 return
1329 if not self.connected: # We went offline during connecting process
1330 if self.on_connect_auth:
1331 self.on_connect_auth(None)
1332 self.on_connect_auth = None
1333 return
1334 if hasattr(con, 'Resource'):
1335 self.server_resource = con.Resource
1336 if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
1337 # Get jid given by server
1338 old_jid = gajim.get_jid_from_account(self.name)
1339 gajim.config.set_per('accounts', self.name, 'name', con.User)
1340 new_jid = gajim.get_jid_from_account(self.name)
1341 gajim.nec.push_incoming_event(AnonymousAuthEvent(None,
1342 conn=self, old_jid=old_jid, new_jid=new_jid))
1343 if auth:
1344 self.last_io = gajim.idlequeue.current_time()
1345 self.connected = 2
1346 self.retrycount = 0
1347 if self.on_connect_auth:
1348 self.on_connect_auth(con)
1349 self.on_connect_auth = None
1350 else:
1351 if not gajim.config.get_per('accounts', self.name, 'savepass'):
1352 # Forget password, it's wrong
1353 self.password = None
1354 gajim.log.debug("Couldn't authenticate to %s" % self._hostname)
1355 self.disconnect(on_purpose = True)
1356 gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
1357 show='offline'))
1358 self.dispatch('ERROR', (_('Authentication failed with "%s"') % \
1359 self._hostname,
1360 _('Please check your login and password for correctness.')))
1361 if self.on_connect_auth:
1362 self.on_connect_auth(None)
1363 self.on_connect_auth = None
1364 # END connect
1366 def add_lang(self, stanza):
1367 if self.lang:
1368 stanza.setAttr('xml:lang', self.lang)
1370 def get_privacy_lists(self):
1371 if not gajim.account_is_connected(self.name):
1372 return
1373 common.xmpp.features_nb.getPrivacyLists(self.connection)
1375 def send_keepalive(self):
1376 # nothing received for the last foo seconds
1377 if self.connection:
1378 self.connection.send(' ')
1380 def _on_xmpp_ping_answer(self, iq_obj):
1381 id_ = unicode(iq_obj.getAttr('id'))
1382 assert id_ == self.awaiting_xmpp_ping_id
1383 self.awaiting_xmpp_ping_id = None
1385 def sendPing(self, pingTo=None):
1387 Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to
1388 server to detect connection failure at application level
1390 if not gajim.account_is_connected(self.name):
1391 return
1392 id_ = self.connection.getAnID()
1393 if pingTo:
1394 to = pingTo.get_full_jid()
1395 gajim.nec.push_incoming_event(PingSentEvent(None, conn=self,
1396 contact=pingTo))
1397 else:
1398 to = gajim.config.get_per('accounts', self.name, 'hostname')
1399 self.awaiting_xmpp_ping_id = id_
1400 iq = common.xmpp.Iq('get', to=to)
1401 iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING)
1402 iq.setID(id_)
1403 def _on_response(resp):
1404 timePong = time_time()
1405 if not common.xmpp.isResultNode(resp):
1406 gajim.nec.push_incoming_event(PingErrorEvent(None, conn=self,
1407 contact=pingTo))
1408 return
1409 timeDiff = round(timePong - timePing, 2)
1410 gajim.nec.push_incoming_event(PingReplyEvent(None, conn=self,
1411 contact=pingTo, seconds=timeDiff))
1412 if pingTo:
1413 timePing = time_time()
1414 self.connection.SendAndCallForResponse(iq, _on_response)
1415 else:
1416 self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer)
1417 gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per(
1418 'accounts', self.name, 'time_for_ping_alive_answer'))
1420 def get_active_default_lists(self):
1421 if not gajim.account_is_connected(self.name):
1422 return
1423 common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
1425 def del_privacy_list(self, privacy_list):
1426 if not gajim.account_is_connected(self.name):
1427 return
1428 def _on_del_privacy_list_result(result):
1429 if result:
1430 gajim.nec.push_incoming_event(PrivacyListRemovedEvent(None,
1431 conn=self, list_name=privacy_list))
1432 else:
1433 self.dispatch('ERROR', (_('Error while removing privacy list'),
1434 _('Privacy list %s has not been removed. It is maybe active in '
1435 'one of your connected resources. Deactivate it and try '
1436 'again.') % privacy_list))
1437 common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list,
1438 _on_del_privacy_list_result)
1440 def get_privacy_list(self, title):
1441 if not gajim.account_is_connected(self.name):
1442 return
1443 common.xmpp.features_nb.getPrivacyList(self.connection, title)
1445 def set_privacy_list(self, listname, tags):
1446 if not gajim.account_is_connected(self.name):
1447 return
1448 common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags)
1450 def set_active_list(self, listname):
1451 if not gajim.account_is_connected(self.name):
1452 return
1453 common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active')
1455 def set_default_list(self, listname):
1456 if not gajim.account_is_connected(self.name):
1457 return
1458 common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
1460 def build_privacy_rule(self, name, action, order=1):
1462 Build a Privacy rule stanza for invisibility
1464 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
1465 l = iq.getTag('query').setTag('list', {'name': name})
1466 i = l.setTag('item', {'action': action, 'order': str(order)})
1467 i.setTag('presence-out')
1468 return iq
1470 def build_invisible_rule(self):
1471 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
1472 l = iq.getTag('query').setTag('list', {'name': 'invisible'})
1473 if self.name in gajim.interface.status_sent_to_groups and \
1474 len(gajim.interface.status_sent_to_groups[self.name]) > 0:
1475 for group in gajim.interface.status_sent_to_groups[self.name]:
1476 i = l.setTag('item', {'type': 'group', 'value': group,
1477 'action': 'allow', 'order': '1'})
1478 i.setTag('presence-out')
1479 if self.name in gajim.interface.status_sent_to_users and \
1480 len(gajim.interface.status_sent_to_users[self.name]) > 0:
1481 for jid in gajim.interface.status_sent_to_users[self.name]:
1482 i = l.setTag('item', {'type': 'jid', 'value': jid,
1483 'action': 'allow', 'order': '2'})
1484 i.setTag('presence-out')
1485 i = l.setTag('item', {'action': 'deny', 'order': '3'})
1486 i.setTag('presence-out')
1487 return iq
1489 def set_invisible_rule(self):
1490 if not gajim.account_is_connected(self.name):
1491 return
1492 iq = self.build_invisible_rule()
1493 self.connection.send(iq)
1495 def activate_privacy_rule(self, name):
1497 Activate a privacy rule
1499 if not gajim.account_is_connected(self.name):
1500 return
1501 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
1502 iq.getTag('query').setTag('active', {'name': name})
1503 self.connection.send(iq)
1505 def send_invisible_presence(self, msg, signed, initial = False):
1506 if not gajim.account_is_connected(self.name):
1507 return
1508 if not self.privacy_rules_supported:
1509 gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
1510 show=gajim.SHOW_LIST[self.connected]))
1511 self.dispatch('ERROR', (_('Invisibility not supported'),
1512 _('Account %s doesn\'t support invisibility.') % self.name))
1513 return
1514 # If we are already connected, and privacy rules are supported, send
1515 # offline presence first as it's required by XEP-0126
1516 if self.connected > 1 and self.privacy_rules_supported:
1517 self.on_purpose = True
1518 p = common.xmpp.Presence(typ = 'unavailable')
1519 p = self.add_sha(p, False)
1520 if msg:
1521 p.setStatus(msg)
1522 self.remove_all_transfers()
1523 self.connection.send(p)
1525 # try to set the privacy rule
1526 iq = self.build_invisible_rule()
1527 self.connection.SendAndCallForResponse(iq, self._continue_invisible,
1528 {'msg': msg, 'signed': signed, 'initial': initial})
1530 def _continue_invisible(self, con, iq_obj, msg, signed, initial):
1531 if iq_obj.getType() == 'error': # server doesn't support privacy lists
1532 return
1533 # active the privacy rule
1534 self.privacy_rules_supported = True
1535 self.activate_privacy_rule('invisible')
1536 self.connected = gajim.SHOW_LIST.index('invisible')
1537 self.status = msg
1538 priority = unicode(gajim.get_priority(self.name, 'invisible'))
1539 p = common.xmpp.Presence(priority = priority)
1540 p = self.add_sha(p, True)
1541 if msg:
1542 p.setStatus(msg)
1543 if signed:
1544 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
1545 self.connection.send(p)
1546 self.priority = priority
1547 gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
1548 show='invisible'))
1549 if initial:
1550 # ask our VCard
1551 self.request_vcard(None)
1553 # Get bookmarks from private namespace
1554 self.get_bookmarks()
1556 # Get annotations
1557 self.get_annotations()
1559 # Inform GUI we just signed in
1560 gajim.nec.push_incoming_event(SignedInEvent(None, conn=self))
1562 def get_signed_presence(self, msg, callback = None):
1563 if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'):
1564 return self.get_signed_msg(msg, callback)
1565 return ''
1567 def connect_and_auth(self):
1568 self.on_connect_success = self._connect_success
1569 self.on_connect_failure = self._connect_failure
1570 self.connect()
1572 def connect_and_init(self, show, msg, sign_msg):
1573 self.continue_connect_info = [show, msg, sign_msg]
1574 self.on_connect_auth = self._discover_server_at_connection
1575 self.connect_and_auth()
1577 def _discover_server_at_connection(self, con):
1578 self.connection = con
1579 if not gajim.account_is_connected(self.name):
1580 return
1581 self.connection.set_send_timeout(self.keepalives, self.send_keepalive)
1582 self.connection.set_send_timeout2(self.pingalives, self.sendPing)
1583 self.connection.onreceive(None)
1585 self.request_message_archiving_preferences()
1587 self.privacy_rules_requested = False
1588 self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'),
1589 id_prefix='Gajim_')
1591 # Discover Stun server(s)
1592 gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(
1593 self.connected_hostname), self._on_stun_resolved)
1595 def _on_stun_resolved(self, host, result_array):
1596 if len(result_array) != 0:
1597 self._stun_servers = self._hosts = [i for i in result_array]
1599 def _request_privacy(self):
1600 iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '')
1601 id_ = self.connection.getAnID()
1602 iq.setID(id_)
1603 self.awaiting_answers[id_] = (PRIVACY_ARRIVED, )
1604 self.connection.send(iq)
1606 def _nec_agent_info_error_received(self, obj):
1607 if obj.conn.name != self.name:
1608 return
1609 if obj.id_[:6] == 'Gajim_':
1610 if not self.privacy_rules_requested:
1611 self.privacy_rules_requested = True
1612 self._request_privacy()
1614 def _nec_agent_info_received(self, obj):
1615 if obj.conn.name != self.name:
1616 return
1617 is_muc = False
1618 transport_type = ''
1619 for identity in obj.identities:
1620 if 'category' in identity and identity['category'] in ('gateway',
1621 'headline') and 'type' in identity:
1622 transport_type = identity['type']
1623 if 'category' in identity and identity['category'] == 'conference' \
1624 and 'type' in identity and identity['type'] == 'text':
1625 is_muc = True
1627 if transport_type and obj.fjid not in gajim.transport_type:
1628 gajim.transport_type[obj.fjid] = transport_type
1629 gajim.logger.save_transport_type(obj.fjid, transport_type)
1631 if obj.id_[:6] == 'Gajim_':
1632 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
1633 our_jid = gajim.get_jid_from_account(self.name)
1634 if obj.fjid == hostname:
1635 if common.xmpp.NS_GMAILNOTIFY in obj.features:
1636 gajim.gmail_domains.append(obj.fjid)
1637 self.request_gmail_notifications()
1638 if common.xmpp.NS_SECLABEL in obj.features:
1639 self.seclabel_supported = True
1640 for identity in obj.identities:
1641 if identity['category'] == 'pubsub' and identity.get(
1642 'type') == 'pep':
1643 self.pep_supported = True
1644 break
1645 if common.xmpp.NS_VCARD in obj.features:
1646 self.vcard_supported = True
1647 if common.xmpp.NS_PUBSUB in obj.features:
1648 self.pubsub_supported = True
1649 if common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS in obj.features:
1650 self.pubsub_publish_options_supported = True
1651 else:
1652 # Remove stored bookmarks accessible to everyone.
1653 self.send_pb_purge(our_jid, 'storage:bookmarks')
1654 self.send_pb_delete(our_jid, 'storage:bookmarks')
1655 if common.xmpp.NS_ARCHIVE in obj.features:
1656 self.archiving_supported = True
1657 if common.xmpp.NS_ARCHIVE_AUTO in obj.features:
1658 self.archive_auto_supported = True
1659 if common.xmpp.NS_ARCHIVE_MANAGE in obj.features:
1660 self.archive_manage_supported = True
1661 if common.xmpp.NS_ARCHIVE_MANUAL in obj.features:
1662 self.archive_manual_supported = True
1663 if common.xmpp.NS_ARCHIVE_PREF in obj.features:
1664 self.archive_pref_supported = True
1665 if common.xmpp.NS_BYTESTREAM in obj.features and \
1666 gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
1667 our_fjid = helpers.parse_jid(our_jid + '/' + \
1668 self.server_resource)
1669 gajim.proxy65_manager.resolve(obj.fjid, self.connection,
1670 our_fjid, self.name)
1671 if common.xmpp.NS_MUC in obj.features and is_muc:
1672 type_ = transport_type or 'jabber'
1673 self.muc_jid[type_] = obj.fjid
1674 if transport_type:
1675 if transport_type in self.available_transports:
1676 self.available_transports[transport_type].append(obj.fjid)
1677 else:
1678 self.available_transports[transport_type] = [obj.fjid]
1679 if not self.privacy_rules_requested:
1680 self.privacy_rules_requested = True
1681 self._request_privacy()
1683 def send_custom_status(self, show, msg, jid):
1684 if not show in gajim.SHOW_LIST:
1685 return -1
1686 if not gajim.account_is_connected(self.name):
1687 return
1688 sshow = helpers.get_xmpp_show(show)
1689 if not msg:
1690 msg = ''
1691 if show == 'offline':
1692 p = common.xmpp.Presence(typ = 'unavailable', to = jid)
1693 p = self.add_sha(p, False)
1694 if msg:
1695 p.setStatus(msg)
1696 else:
1697 signed = self.get_signed_presence(msg)
1698 priority = unicode(gajim.get_priority(self.name, sshow))
1699 p = common.xmpp.Presence(typ = None, priority = priority, show = sshow,
1700 to = jid)
1701 p = self.add_sha(p)
1702 if msg:
1703 p.setStatus(msg)
1704 if signed:
1705 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
1706 self.connection.send(p)
1708 def _change_to_invisible(self, msg):
1709 signed = self.get_signed_presence(msg)
1710 self.send_invisible_presence(msg, signed)
1712 def _change_from_invisible(self):
1713 if self.privacy_rules_supported:
1714 iq = self.build_privacy_rule('visible', 'allow')
1715 self.connection.send(iq)
1716 self.activate_privacy_rule('visible')
1718 def _update_status(self, show, msg):
1719 xmpp_show = helpers.get_xmpp_show(show)
1720 priority = unicode(gajim.get_priority(self.name, xmpp_show))
1721 p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show)
1722 p = self.add_sha(p)
1723 if msg:
1724 p.setStatus(msg)
1725 signed = self.get_signed_presence(msg)
1726 if signed:
1727 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
1728 if self.connection:
1729 self.connection.send(p)
1730 self.priority = priority
1731 gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
1732 show=show))
1734 def send_motd(self, jid, subject = '', msg = '', xhtml = None):
1735 if not gajim.account_is_connected(self.name):
1736 return
1737 msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject,
1738 xhtml = xhtml)
1740 self.connection.send(msg_iq)
1742 def send_message(self, jid, msg, keyID=None, type_='chat', subject='',
1743 chatstate=None, msg_id=None, composing_xep=None, resource=None,
1744 user_nick=None, xhtml=None, label=None, session=None, forward_from=None,
1745 form_node=None, original_message=None, delayed=None, callback=None,
1746 callback_args=[], now=False):
1748 def cb(jid, msg, keyID, forward_from, session, original_message,
1749 subject, type_, msg_iq):
1750 msg_id = self.connection.send(msg_iq, now=now)
1751 jid = helpers.parse_jid(jid)
1752 gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
1753 jid=jid, message=msg, keyID=keyID, chatstate=chatstate))
1754 if callback:
1755 callback(msg_id, *callback_args)
1757 self.log_message(jid, msg, forward_from, session, original_message,
1758 subject, type_)
1760 self._prepare_message(jid, msg, keyID, type_=type_, subject=subject,
1761 chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
1762 resource=resource, user_nick=user_nick, xhtml=xhtml, label=label,
1763 session=session, forward_from=forward_from, form_node=form_node,
1764 original_message=original_message, delayed=delayed, callback=cb)
1766 def send_contacts(self, contacts, jid):
1768 Send contacts with RosterX (Xep-0144)
1770 if not gajim.account_is_connected(self.name):
1771 return
1772 if len(contacts) == 1:
1773 msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(),
1774 contacts[0].get_shown_name())
1775 else:
1776 msg = _('Sent contacts:')
1777 for contact in contacts:
1778 msg += '\n "%s" (%s)' % (contact.get_full_jid(),
1779 contact.get_shown_name())
1780 msg_iq = common.xmpp.Message(to=jid, body=msg)
1781 x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX)
1782 for contact in contacts:
1783 x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid,
1784 'name': contact.get_shown_name()})
1785 self.connection.send(msg_iq)
1787 def send_stanza(self, stanza):
1789 Send a stanza untouched
1791 if not self.connection:
1792 return
1793 self.connection.send(stanza)
1795 def ack_subscribed(self, jid):
1796 if not gajim.account_is_connected(self.name):
1797 return
1798 log.debug('ack\'ing subscription complete for %s' % jid)
1799 p = common.xmpp.Presence(jid, 'subscribe')
1800 self.connection.send(p)
1802 def ack_unsubscribed(self, jid):
1803 if not gajim.account_is_connected(self.name):
1804 return
1805 log.debug('ack\'ing unsubscription complete for %s' % jid)
1806 p = common.xmpp.Presence(jid, 'unsubscribe')
1807 self.connection.send(p)
1809 def request_subscription(self, jid, msg='', name='', groups=[],
1810 auto_auth=False, user_nick=''):
1811 if not gajim.account_is_connected(self.name):
1812 return
1813 log.debug('subscription request for %s' % jid)
1814 if auto_auth:
1815 self.jids_for_auto_auth.append(jid)
1816 # RFC 3921 section 8.2
1817 infos = {'jid': jid}
1818 if name:
1819 infos['name'] = name
1820 iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER)
1821 q = iq.getTag('query')
1822 item = q.addChild('item', attrs=infos)
1823 for g in groups:
1824 item.addChild('group').setData(g)
1825 self.connection.send(iq)
1827 p = common.xmpp.Presence(jid, 'subscribe')
1828 if user_nick:
1829 p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick)
1830 p = self.add_sha(p)
1831 if msg:
1832 p.setStatus(msg)
1833 self.connection.send(p)
1835 def send_authorization(self, jid):
1836 if not gajim.account_is_connected(self.name):
1837 return
1838 p = common.xmpp.Presence(jid, 'subscribed')
1839 p = self.add_sha(p)
1840 self.connection.send(p)
1842 def refuse_authorization(self, jid):
1843 if not gajim.account_is_connected(self.name):
1844 return
1845 p = common.xmpp.Presence(jid, 'unsubscribed')
1846 p = self.add_sha(p)
1847 self.connection.send(p)
1849 def unsubscribe(self, jid, remove_auth = True):
1850 if not gajim.account_is_connected(self.name):
1851 return
1852 if remove_auth:
1853 self.connection.getRoster().delItem(jid)
1854 jid_list = gajim.config.get_per('contacts')
1855 for j in jid_list:
1856 if j.startswith(jid):
1857 gajim.config.del_per('contacts', j)
1858 else:
1859 self.connection.getRoster().Unsubscribe(jid)
1860 self.update_contact(jid, '', [])
1862 def unsubscribe_agent(self, agent):
1863 if not gajim.account_is_connected(self.name):
1864 return
1865 iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
1866 iq.getTag('query').setTag('remove')
1867 id_ = self.connection.getAnID()
1868 iq.setID(id_)
1869 self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
1870 self.connection.send(iq)
1871 self.connection.getRoster().delItem(agent)
1873 def send_new_account_infos(self, form, is_form):
1874 if is_form:
1875 # Get username and password and put them in new_account_info
1876 for field in form.iter_fields():
1877 if field.var == 'username':
1878 self.new_account_info['name'] = field.value
1879 if field.var == 'password':
1880 self.new_account_info['password'] = field.value
1881 else:
1882 # Get username and password and put them in new_account_info
1883 if 'username' in form:
1884 self.new_account_info['name'] = form['username']
1885 if 'password' in form:
1886 self.new_account_info['password'] = form['password']
1887 self.new_account_form = form
1888 self.new_account(self.name, self.new_account_info)
1890 def new_account(self, name, config, sync=False):
1891 # If a connection already exist we cannot create a new account
1892 if self.connection:
1893 return
1894 self._hostname = config['hostname']
1895 self.new_account_info = config
1896 self.name = name
1897 self.on_connect_success = self._on_new_account
1898 self.on_connect_failure = self._on_new_account
1899 self.connect(config)
1901 def _on_new_account(self, con=None, con_type=None):
1902 if not con_type:
1903 if len(self._connection_types) or len(self._hosts):
1904 # There are still other way to try to connect
1905 return
1906 reason = _('Could not connect to "%s"') % self._hostname
1907 gajim.nec.push_incoming_event(NewAccountNotConnectedEvent(None,
1908 conn=self, reason=reason))
1909 return
1910 self.on_connect_failure = None
1911 self.connection = con
1912 common.xmpp.features_nb.getRegInfo(con, self._hostname)
1914 def request_os_info(self, jid, resource, groupchat_jid=None):
1916 groupchat_jid is used when we want to send a request to a real jid and
1917 act as if the answer comes from the groupchat_jid
1919 if not gajim.account_is_connected(self.name):
1920 return
1921 # If we are invisible, do not request
1922 if self.connected == gajim.SHOW_LIST.index('invisible'):
1923 self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status')))
1924 return
1925 to_whom_jid = jid
1926 if resource:
1927 to_whom_jid += '/' + resource
1928 iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\
1929 common.xmpp.NS_VERSION)
1930 id_ = self.connection.getAnID()
1931 iq.setID(id_)
1932 if groupchat_jid:
1933 self.groupchat_jids[id_] = groupchat_jid
1934 self.version_ids.append(id_)
1935 self.connection.send(iq)
1937 def request_entity_time(self, jid, resource, groupchat_jid=None):
1939 groupchat_jid is used when we want to send a request to a real jid and
1940 act as if the answer comes from the groupchat_jid
1942 if not gajim.account_is_connected(self.name):
1943 return
1944 # If we are invisible, do not request
1945 if self.connected == gajim.SHOW_LIST.index('invisible'):
1946 self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status')))
1947 return
1948 to_whom_jid = jid
1949 if resource:
1950 to_whom_jid += '/' + resource
1951 iq = common.xmpp.Iq(to=to_whom_jid, typ='get')
1952 iq.addChild('time', namespace=common.xmpp.NS_TIME_REVISED)
1953 id_ = self.connection.getAnID()
1954 iq.setID(id_)
1955 if groupchat_jid:
1956 self.groupchat_jids[id_] = groupchat_jid
1957 self.entity_time_ids.append(id_)
1958 self.connection.send(iq)
1960 def request_gateway_prompt(self, jid, prompt=None):
1961 def _on_prompt_result(resp):
1962 gajim.nec.push_incoming_event(GatewayPromptReceivedEvent(None,
1963 conn=self, stanza=resp))
1964 if prompt:
1965 typ_ = 'set'
1966 else:
1967 typ_ = 'get'
1968 iq = common.xmpp.Iq(typ=typ_, to=jid)
1969 query = iq.addChild(name='query', namespace=common.xmpp.NS_GATEWAY)
1970 if prompt:
1971 query.setTagData('prompt', prompt)
1972 self.connection.SendAndCallForResponse(iq, _on_prompt_result)
1974 def get_settings(self):
1976 Get Gajim settings as described in XEP 0049
1978 if not gajim.account_is_connected(self.name):
1979 return
1980 iq = common.xmpp.Iq(typ='get')
1981 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
1982 iq2.addChild(name='gajim', namespace='gajim:prefs')
1983 self.connection.send(iq)
1985 def seclabel_catalogue(self, to, callback):
1986 if not gajim.account_is_connected(self.name):
1987 return
1988 self.seclabel_catalogue_request(to, callback)
1989 iq = common.xmpp.Iq(typ='get')
1990 iq2 = iq.addChild(name='catalog', namespace=common.xmpp.NS_SECLABEL_CATALOG)
1991 iq2.setAttr('to', to)
1992 self.connection.send(iq)
1994 def _nec_privacy_list_received(self, obj):
1995 if obj.conn.name != self.name:
1996 return
1997 if obj.list_name != 'block':
1998 return
1999 self.blocked_contacts = []
2000 self.blocked_groups = []
2001 self.blocked_list = []
2002 self.blocked_all = False
2003 for rule in obj.rules:
2004 if rule['action'] == 'allow':
2005 if not 'type' in rule:
2006 self.blocked_all = False
2007 elif rule['type'] == 'jid' and rule['value'] in \
2008 self.blocked_contacts:
2009 self.blocked_contacts.remove(rule['value'])
2010 elif rule['type'] == 'group' and rule['value'] in \
2011 self.blocked_groups:
2012 self.blocked_groups.remove(rule['value'])
2013 elif rule['action'] == 'deny':
2014 if not 'type' in rule:
2015 self.blocked_all = True
2016 elif rule['type'] == 'jid' and rule['value'] not in \
2017 self.blocked_contacts:
2018 self.blocked_contacts.append(rule['value'])
2019 elif rule['type'] == 'group' and rule['value'] not in \
2020 self.blocked_groups:
2021 self.blocked_groups.append(rule['value'])
2022 self.blocked_list.append(rule)
2024 def _request_bookmarks_xml(self):
2025 if not gajim.account_is_connected(self.name):
2026 return
2027 iq = common.xmpp.Iq(typ='get')
2028 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
2029 iq2.addChild(name='storage', namespace='storage:bookmarks')
2030 self.connection.send(iq)
2032 def _check_bookmarks_received(self):
2033 if not self.bookmarks:
2034 self._request_bookmarks_xml()
2036 def get_bookmarks(self, storage_type=None):
2038 Get Bookmarks from storage or PubSub if supported as described in XEP
2039 0048
2041 storage_type can be set to xml to force request to xml storage
2043 if not gajim.account_is_connected(self.name):
2044 return
2045 if self.pubsub_supported and storage_type != 'xml':
2046 self.send_pb_retrieve('', 'storage:bookmarks')
2047 # some server (ejabberd) are so slow to answer that we request via XML
2048 # if we don't get answer in the next 30 seconds
2049 gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30)
2050 else:
2051 self._request_bookmarks_xml()
2053 def store_bookmarks(self, storage_type=None):
2055 Send bookmarks to the storage namespace or PubSub if supported
2057 storage_type can be set to 'pubsub' or 'xml' so store in only one method
2058 else it will be stored on both
2060 if not gajim.account_is_connected(self.name):
2061 return
2062 iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'})
2063 for bm in self.bookmarks:
2064 iq2 = iq.addChild(name = "conference")
2065 iq2.setAttr('jid', bm['jid'])
2066 iq2.setAttr('autojoin', bm['autojoin'])
2067 iq2.setAttr('minimize', bm['minimize'])
2068 iq2.setAttr('name', bm['name'])
2069 # Only add optional elements if not empty
2070 # Note: need to handle both None and '' as empty
2071 # thus shouldn't use "is not None"
2072 if bm.get('nick', None):
2073 iq2.setTagData('nick', bm['nick'])
2074 if bm.get('password', None):
2075 iq2.setTagData('password', bm['password'])
2076 if bm.get('print_status', None):
2077 iq2.setTagData('print_status', bm['print_status'])
2079 if self.pubsub_supported and self.pubsub_publish_options_supported and \
2080 storage_type != 'xml':
2081 options = common.xmpp.Node(common.xmpp.NS_DATA + ' x',
2082 attrs={'type': 'submit'})
2083 f = options.addChild('field', attrs={'var': 'FORM_TYPE',
2084 'type': 'hidden'})
2085 f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS)
2086 f = options.addChild('field', attrs={'var': 'pubsub#persist_items'})
2087 f.setTagData('value', 'true')
2088 f = options.addChild('field', attrs={'var': 'pubsub#access_model'})
2089 f.setTagData('value', 'whitelist')
2090 self.send_pb_publish('', 'storage:bookmarks', iq, 'current',
2091 options=options)
2092 if storage_type != 'pubsub':
2093 iqA = common.xmpp.Iq(typ='set')
2094 iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
2095 iqB.addChild(node=iq)
2096 self.connection.send(iqA)
2098 def get_annotations(self):
2100 Get Annonations from storage as described in XEP 0048, and XEP 0145
2102 self.annotations = {}
2103 if not gajim.account_is_connected(self.name):
2104 return
2105 iq = common.xmpp.Iq(typ='get')
2106 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
2107 iq2.addChild(name='storage', namespace='storage:rosternotes')
2108 self.connection.send(iq)
2110 def store_annotations(self):
2112 Set Annonations in private storage as described in XEP 0048, and XEP 0145
2114 if not gajim.account_is_connected(self.name):
2115 return
2116 iq = common.xmpp.Iq(typ='set')
2117 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
2118 iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes')
2119 for jid in self.annotations.keys():
2120 if self.annotations[jid]:
2121 iq4 = iq3.addChild(name = "note")
2122 iq4.setAttr('jid', jid)
2123 iq4.setData(self.annotations[jid])
2124 self.connection.send(iq)
2126 def get_roster_delimiter(self):
2128 Get roster group delimiter from storage as described in XEP 0083
2130 if not gajim.account_is_connected(self.name):
2131 return
2132 iq = common.xmpp.Iq(typ='get')
2133 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
2134 iq2.addChild(name='roster', namespace='roster:delimiter')
2135 id_ = self.connection.getAnID()
2136 iq.setID(id_)
2137 self.awaiting_answers[id_] = (DELIMITER_ARRIVED, )
2138 self.connection.send(iq)
2140 def set_roster_delimiter(self, delimiter='::'):
2142 Set roster group delimiter to the storage namespace
2144 if not gajim.account_is_connected(self.name):
2145 return
2146 iq = common.xmpp.Iq(typ='set')
2147 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
2148 iq3 = iq2.addChild(name='roster', namespace='roster:delimiter')
2149 iq3.setData(delimiter)
2151 self.connection.send(iq)
2153 def get_metacontacts(self):
2155 Get metacontacts list from storage as described in XEP 0049
2157 if not gajim.account_is_connected(self.name):
2158 return
2159 iq = common.xmpp.Iq(typ='get')
2160 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
2161 iq2.addChild(name='storage', namespace='storage:metacontacts')
2162 id_ = self.connection.getAnID()
2163 iq.setID(id_)
2164 self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, )
2165 self.connection.send(iq)
2167 def store_metacontacts(self, tags_list):
2169 Send meta contacts to the storage namespace
2171 if not gajim.account_is_connected(self.name):
2172 return
2173 iq = common.xmpp.Iq(typ='set')
2174 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
2175 iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts')
2176 for tag in tags_list:
2177 for data in tags_list[tag]:
2178 jid = data['jid']
2179 dict_ = {'jid': jid, 'tag': tag}
2180 if 'order' in data:
2181 dict_['order'] = data['order']
2182 iq3.addChild(name = 'meta', attrs = dict_)
2183 self.connection.send(iq)
2185 def request_roster(self):
2186 version = None
2187 features = self.connection.Dispatcher.Stream.features
2188 if features and features.getTag('ver',
2189 namespace=common.xmpp.NS_ROSTER_VER):
2190 version = gajim.config.get_per('accounts', self.name,
2191 'roster_version')
2192 if version and not gajim.contacts.get_contacts_jid_list(
2193 self.name):
2194 gajim.config.set_per('accounts', self.name, 'roster_version',
2196 version = None
2198 iq_id = self.connection.initRoster(version=version)
2199 self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
2201 def send_agent_status(self, agent, ptype):
2202 if not gajim.account_is_connected(self.name):
2203 return
2204 show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
2205 p = common.xmpp.Presence(to = agent, typ = ptype, show = show)
2206 p = self.add_sha(p, ptype != 'unavailable')
2207 self.connection.send(p)
2209 def send_captcha(self, jid, form_node):
2210 if not gajim.account_is_connected(self.name):
2211 return
2212 iq = common.xmpp.Iq(typ='set', to=jid)
2213 captcha = iq.addChild(name='captcha', namespace=common.xmpp.NS_CAPTCHA)
2214 captcha.addChild(node=form_node)
2215 self.connection.send(iq)
2217 def check_unique_room_id_support(self, server, instance):
2218 if not gajim.account_is_connected(self.name):
2219 return
2220 iq = common.xmpp.Iq(typ = 'get', to = server)
2221 iq.setAttr('id', 'unique1')
2222 iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE)
2223 def _on_response(resp):
2224 if not common.xmpp.isResultNode(resp):
2225 gajim.nec.push_incoming_event(UniqueRoomIdNotSupportedEvent(
2226 None, conn=self, instance=instance, server=server))
2227 return
2228 gajim.nec.push_incoming_event(UniqueRoomIdSupportedEvent(None,
2229 conn=self, instance=instance, server=server,
2230 room_id=resp.getTag('unique').getData()))
2231 self.connection.SendAndCallForResponse(iq, _on_response)
2233 def join_gc(self, nick, room_jid, password, change_nick=False):
2234 # FIXME: This room JID needs to be normalized; see #1364
2235 if not gajim.account_is_connected(self.name):
2236 return
2237 show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
2238 if show == 'invisible':
2239 # Never join a room when invisible
2240 return
2242 # last date/time in history to avoid duplicate
2243 if room_jid not in self.last_history_time:
2244 # Not in memory, get it from DB
2245 last_log = None
2246 # Do not check if we are not logging for this room
2247 if gajim.config.should_log(self.name, room_jid):
2248 # Check time first in the FAST table
2249 last_log = gajim.logger.get_room_last_message_time(room_jid)
2250 if last_log is None:
2251 # Not in special table, get it from messages DB
2252 last_log = gajim.logger.get_last_date_that_has_logs(room_jid,
2253 is_room=True)
2254 # Create self.last_history_time[room_jid] even if not logging,
2255 # could be used in connection_handlers
2256 if last_log is None:
2257 last_log = 0
2258 self.last_history_time[room_jid] = last_log
2260 p = common.xmpp.Presence(to='%s/%s' % (room_jid, nick),
2261 show=show, status=self.status)
2262 h = hmac.new(self.secret_hmac, room_jid).hexdigest()[:6]
2263 id_ = self.connection.getAnID()
2264 id_ = 'gajim_muc_' + id_ + '_' + h
2265 p.setID(id_)
2266 if gajim.config.get('send_sha_in_gc_presence'):
2267 p = self.add_sha(p)
2268 self.add_lang(p)
2269 if not change_nick:
2270 t = p.setTag(common.xmpp.NS_MUC + ' x')
2271 last_date = self.last_history_time[room_jid]
2272 if last_date == 0:
2273 last_date = time.time() - gajim.config.get(
2274 'muc_restore_timeout') * 60
2275 else:
2276 last_date = min(last_date, time.time() - gajim.config.get(
2277 'muc_restore_timeout') * 60)
2278 last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(last_date))
2279 t.setTag('history', {'maxstanzas': gajim.config.get(
2280 'muc_restore_lines'), 'since': last_date})
2281 if password:
2282 t.setTagData('password', password)
2283 self.connection.send(p)
2285 def send_gc_message(self, jid, msg, xhtml = None, label = None):
2286 if not gajim.account_is_connected(self.name):
2287 return
2288 if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
2289 from common.rst_xhtml_generator import create_xhtml
2290 xhtml = create_xhtml(msg)
2291 msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml)
2292 if label is not None:
2293 msg_iq.addChild(node = label)
2294 self.connection.send(msg_iq)
2295 gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
2296 jid=jid, message=msg, keyID=None))
2298 def send_gc_subject(self, jid, subject):
2299 if not gajim.account_is_connected(self.name):
2300 return
2301 msg_iq = common.xmpp.Message(jid, typ = 'groupchat', subject = subject)
2302 self.connection.send(msg_iq)
2304 def request_gc_config(self, room_jid):
2305 if not gajim.account_is_connected(self.name):
2306 return
2307 iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER,
2308 to = room_jid)
2309 self.add_lang(iq)
2310 self.connection.send(iq)
2312 def destroy_gc_room(self, room_jid, reason = '', jid = ''):
2313 if not gajim.account_is_connected(self.name):
2314 return
2315 iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER,
2316 to = room_jid)
2317 destroy = iq.getTag('query').setTag('destroy')
2318 if reason:
2319 destroy.setTagData('reason', reason)
2320 if jid:
2321 destroy.setAttr('jid', jid)
2322 self.connection.send(iq)
2324 def send_gc_status(self, nick, jid, show, status):
2325 if not gajim.account_is_connected(self.name):
2326 return
2327 if show == 'invisible':
2328 show = 'offline'
2329 ptype = None
2330 if show == 'offline':
2331 ptype = 'unavailable'
2332 xmpp_show = helpers.get_xmpp_show(show)
2333 p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype,
2334 show = xmpp_show, status = status)
2335 h = hmac.new(self.secret_hmac, jid).hexdigest()[:6]
2336 id_ = self.connection.getAnID()
2337 id_ = 'gajim_muc_' + id_ + '_' + h
2338 p.setID(id_)
2339 if gajim.config.get('send_sha_in_gc_presence') and show != 'offline':
2340 p = self.add_sha(p, ptype != 'unavailable')
2341 self.add_lang(p)
2342 # send instantly so when we go offline, status is sent to gc before we
2343 # disconnect from jabber server
2344 self.connection.send(p)
2346 def gc_got_disconnected(self, room_jid):
2348 A groupchat got disconnected. This can be or purpose or not
2350 Save the time we had last message to avoid duplicate logs AND be faster
2351 than get that date from DB. Save time that we have in mem in a small
2352 table (with fast access)
2354 gajim.logger.set_room_last_message_time(room_jid, self.last_history_time[room_jid])
2356 def gc_set_role(self, room_jid, nick, role, reason = ''):
2358 Role is for all the life of the room so it's based on nick
2360 if not gajim.account_is_connected(self.name):
2361 return
2362 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
2363 common.xmpp.NS_MUC_ADMIN)
2364 item = iq.getTag('query').setTag('item')
2365 item.setAttr('nick', nick)
2366 item.setAttr('role', role)
2367 if reason:
2368 item.addChild(name = 'reason', payload = reason)
2369 self.connection.send(iq)
2371 def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
2373 Affiliation is for all the life of the room so it's based on jid
2375 if not gajim.account_is_connected(self.name):
2376 return
2377 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
2378 common.xmpp.NS_MUC_ADMIN)
2379 item = iq.getTag('query').setTag('item')
2380 item.setAttr('jid', jid)
2381 item.setAttr('affiliation', affiliation)
2382 if reason:
2383 item.addChild(name = 'reason', payload = reason)
2384 self.connection.send(iq)
2386 def send_gc_affiliation_list(self, room_jid, users_dict):
2387 if not gajim.account_is_connected(self.name):
2388 return
2389 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \
2390 common.xmpp.NS_MUC_ADMIN)
2391 item = iq.getTag('query')
2392 for jid in users_dict:
2393 item_tag = item.addChild('item', {'jid': jid,
2394 'affiliation': users_dict[jid]['affiliation']})
2395 if 'reason' in users_dict[jid] and users_dict[jid]['reason']:
2396 item_tag.setTagData('reason', users_dict[jid]['reason'])
2397 self.connection.send(iq)
2399 def get_affiliation_list(self, room_jid, affiliation):
2400 if not gajim.account_is_connected(self.name):
2401 return
2402 iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \
2403 common.xmpp.NS_MUC_ADMIN)
2404 item = iq.getTag('query').setTag('item')
2405 item.setAttr('affiliation', affiliation)
2406 self.connection.send(iq)
2408 def send_gc_config(self, room_jid, form):
2409 if not gajim.account_is_connected(self.name):
2410 return
2411 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
2412 common.xmpp.NS_MUC_OWNER)
2413 query = iq.getTag('query')
2414 form.setAttr('type', 'submit')
2415 query.addChild(node = form)
2416 self.connection.send(iq)
2418 def change_password(self, password):
2419 if not gajim.account_is_connected(self.name):
2420 return
2421 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
2422 username = gajim.config.get_per('accounts', self.name, 'name')
2423 iq = common.xmpp.Iq(typ = 'set', to = hostname)
2424 q = iq.setTag(common.xmpp.NS_REGISTER + ' query')
2425 q.setTagData('username', username)
2426 q.setTagData('password', password)
2427 self.connection.send(iq)
2429 def get_password(self, callback, type_):
2430 self.pasword_callback = (callback, type_)
2431 if self.password:
2432 self.set_password(self.password)
2433 return
2434 gajim.nec.push_incoming_event(PasswordRequiredEvent(None, conn=self))
2436 def set_password(self, password):
2437 self.password = password
2438 if self.pasword_callback:
2439 callback, type_ = self.pasword_callback
2440 if self._current_type == 'plain' and type_ == 'PLAIN' and \
2441 gajim.config.get_per('accounts', self.name,
2442 'warn_when_insecure_password'):
2443 gajim.nec.push_incoming_event(InsecurePasswordEvent(None,
2444 conn=self))
2445 return
2446 callback(password)
2447 self.pasword_callback = None
2449 def accept_insecure_password(self):
2450 if self.pasword_callback:
2451 callback, type_ = self.pasword_callback
2452 callback(self.password)
2453 self.pasword_callback = None
2455 def unregister_account(self, on_remove_success):
2456 # no need to write this as a class method and keep the value of
2457 # on_remove_success as a class property as pass it as an argument
2458 def _on_unregister_account_connect(con):
2459 self.on_connect_auth = None
2460 if gajim.account_is_connected(self.name):
2461 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
2462 iq = common.xmpp.Iq(typ = 'set', to = hostname)
2463 id_ = self.connection.getAnID()
2464 iq.setID(id_)
2465 iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove')
2466 def _on_answer(con, result):
2467 if result.getID() == id_:
2468 on_remove_success(True)
2469 return
2470 self.dispatch('ERROR', (_('Unregister failed'),
2471 _('Unregistration with server %(server)s failed: '
2472 '%(error)s') % {'server': hostname,
2473 'error': result.getErrorMsg()}))
2474 on_remove_success(False)
2475 con.RegisterHandler('iq', _on_answer, 'result', system=True)
2476 con.SendAndWaitForResponse(iq)
2477 return
2478 on_remove_success(False)
2479 if self.connected == 0:
2480 self.on_connect_auth = _on_unregister_account_connect
2481 self.connect_and_auth()
2482 else:
2483 _on_unregister_account_connect(self.connection)
2485 def send_invite(self, room, to, reason='', continue_tag=False):
2487 Send invitation
2489 message=common.xmpp.Message(to = room)
2490 c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER)
2491 c = c.addChild(name = 'invite', attrs={'to' : to})
2492 if continue_tag:
2493 c.addChild(name = 'continue')
2494 if reason != '':
2495 c.setTagData('reason', reason)
2496 self.connection.send(message)
2498 def check_pingalive(self):
2499 if self.awaiting_xmpp_ping_id:
2500 # We haven't got the pong in time, disco and reconnect
2501 log.warn("No reply received for keepalive ping. Reconnecting.")
2502 self._disconnectedReconnCB()
2504 def _reconnect_alarm(self):
2505 if not gajim.config.get_per('accounts', self.name, 'active'):
2506 # Account may have been disabled
2507 return
2508 if self.time_to_reconnect:
2509 if self.connected < 2:
2510 self._reconnect()
2511 else:
2512 self.time_to_reconnect = None
2514 def request_search_fields(self, jid):
2515 iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \
2516 common.xmpp.NS_SEARCH)
2517 self.connection.send(iq)
2519 def send_search_form(self, jid, form, is_form):
2520 iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \
2521 common.xmpp.NS_SEARCH)
2522 item = iq.getTag('query')
2523 if is_form:
2524 item.addChild(node=form)
2525 else:
2526 for i in form.keys():
2527 item.setTagData(i, form[i])
2528 def _on_response(resp):
2529 gajim.nec.push_incoming_event(SearchResultReceivedEvent(None,
2530 conn=self, stanza=resp))
2532 self.connection.SendAndCallForResponse(iq, _on_response)
2534 def load_roster_from_db(self):
2535 gajim.nec.push_incoming_event(RosterReceivedEvent(None, conn=self))
2537 # END Connection