Package common :: Module connection
[hide private]
[frames] | no frames]

Source Code for Module common.connection

   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> 
  19  ## 
  20  ## This file is part of Gajim. 
  21  ## 
  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. 
  25  ## 
  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. 
  30  ## 
  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/>. 
  33  ## 
  34   
  35  import os 
  36  import random 
  37  import socket 
  38  import operator 
  39   
  40  import time 
  41  import locale 
  42  import hmac 
  43   
  44  try: 
  45      randomsource = random.SystemRandom() 
  46  except Exception: 
  47      randomsource = random.Random() 
  48      randomsource.seed() 
  49   
  50  import signal 
  51  if os.name != 'nt': 
  52      signal.signal(signal.SIGPIPE, signal.SIG_DFL) 
  53   
  54  import common.xmpp 
  55  from common import helpers 
  56  from common import gajim 
  57  from common import GnuPG 
  58  from common import passwords 
  59  from common import exceptions 
  60   
  61  from connection_handlers import * 
  62   
  63  from string import Template 
  64  import logging 
  65  log = logging.getLogger('gajim.c.connection') 
  66   
  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") 
 100  } 
 101   
102 -class CommonConnection:
103 """ 104 Common connection class, can be derivated for normal connection or zeroconf 105 connection 106 """ 107
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 = GnuPG.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 = [] 132 133 self.blocked_list = [] 134 self.blocked_contacts = [] 135 self.blocked_groups = [] 136 self.blocked_all = False 137 138 self.seclabel_supported = False 139 self.seclabel_catalogues = {} 140 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 145 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} 152 153 self.privacy_rules_supported = False 154 self.vcard_supported = False 155 self.private_storage_supported = False 156 157 self.muc_jid = {} # jid of muc server for each transport type 158 self._stun_servers = [] # STUN servers of our jabber server 159 160 self.awaiting_cids = {} # Used for XEP-0231 161 162 self.get_config_values_or_default()
163
164 - def _compute_resource(self):
165 resource = gajim.config.get_per('accounts', self.name, 'resource') 166 # All valid resource substitution strings should be added to this hash. 167 if resource: 168 resource = Template(resource).safe_substitute({ 169 'hostname': socket.gethostname() 170 }) 171 return resource
172
173 - def dispatch(self, event, data):
174 """ 175 Always passes account name as first param 176 """ 177 gajim.ged.raise_event(event, self.name, data)
178
179 - def _reconnect(self):
180 """ 181 To be implemented by derivated classes 182 """ 183 raise NotImplementedError
184
185 - def quit(self, kill_core):
186 if kill_core and gajim.account_is_connected(self.name): 187 self.disconnect(on_purpose=True)
188
189 - def test_gpg_passphrase(self, password):
190 """ 191 Returns 'ok', 'bad_pass' or 'expired' 192 """ 193 if not self.gpg: 194 return False 195 self.gpg.passphrase = password 196 keyID = gajim.config.get_per('accounts', self.name, 'keyid') 197 signed = self.gpg.sign('test', keyID) 198 self.gpg.password = None 199 if signed == 'KEYEXPIRED': 200 return 'expired' 201 elif signed == 'BAD_PASSPHRASE': 202 return 'bad_pass' 203 return 'ok'
204
205 - def get_signed_msg(self, msg, callback = None):
206 """ 207 Returns the signed message if possible or an empty string if gpg is not 208 used or None if waiting for passphrase 209 210 callback is the function to call when user give the passphrase 211 """ 212 signed = '' 213 keyID = gajim.config.get_per('accounts', self.name, 'keyid') 214 if keyID and self.USE_GPG: 215 use_gpg_agent = gajim.config.get('use_gpg_agent') 216 if self.gpg.passphrase is None and not use_gpg_agent: 217 # We didn't set a passphrase 218 return None 219 if self.gpg.passphrase is not None or use_gpg_agent: 220 signed = self.gpg.sign(msg, keyID) 221 if signed == 'BAD_PASSPHRASE': 222 self.USE_GPG = False 223 signed = '' 224 self.dispatch('BAD_PASSPHRASE', ()) 225 return signed
226
227 - def _on_disconnected(self):
228 """ 229 Called when a disconnect request has completed successfully 230 """ 231 self.disconnect(on_purpose=True) 232 self.dispatch('STATUS', 'offline')
233
234 - def get_status(self):
235 return gajim.SHOW_LIST[self.connected]
236
237 - def check_jid(self, jid):
238 """ 239 This function must be implemented by derivated classes. It has to return 240 the valid jid, or raise a helpers.InvalidFormat exception 241 """ 242 raise NotImplementedError
243
244 - def _prepare_message(self, jid, msg, keyID, type_='chat', subject='', 245 chatstate=None, msg_id=None, composing_xep=None, resource=None, 246 user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, 247 label=None, original_message=None, delayed=None, callback=None):
248 if not self.connection or self.connected < 2: 249 return 1 250 try: 251 jid = self.check_jid(jid) 252 except helpers.InvalidFormat: 253 self.dispatch('ERROR', (_('Invalid Jabber ID'), 254 _('It is not possible to send a message to %s, this JID is not ' 255 'valid.') % jid)) 256 return 257 258 if msg and not xhtml and gajim.config.get( 259 'rst_formatting_outgoing_messages'): 260 from common.rst_xhtml_generator import create_xhtml 261 xhtml = create_xhtml(msg) 262 if not msg and chatstate is None and form_node is None: 263 return 264 fjid = jid 265 if resource: 266 fjid += '/' + resource 267 msgtxt = msg 268 msgenc = '' 269 270 if session: 271 fjid = session.get_to() 272 273 if keyID and self.USE_GPG: 274 xhtml = None 275 if keyID == 'UNKNOWN': 276 error = _('Neither the remote presence is signed, nor a key was ' 277 'assigned.') 278 elif keyID.endswith('MISMATCH'): 279 error = _('The contact\'s key (%s) does not match the key assigned ' 280 'in Gajim.' % keyID[:8]) 281 else: 282 def encrypt_thread(msg, keyID, always_trust=False): 283 # encrypt message. This function returns (msgenc, error) 284 return self.gpg.encrypt(msg, [keyID], always_trust)
285 def _on_encrypted(output): 286 msgenc, error = output 287 if error == 'NOT_TRUSTED': 288 def _on_always_trust(answer): 289 if answer: 290 gajim.thread_interface(encrypt_thread, [msg, keyID, 291 True], _on_encrypted, []) 292 else: 293 self._message_encrypted_cb(output, type_, msg, 294 msgtxt, original_message, fjid, resource, 295 jid, xhtml, subject, chatstate, msg_id, 296 composing_xep, label, forward_from, delayed, 297 session, form_node, user_nick, keyID, 298 callback)
299 self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust) 300 else: 301 self._message_encrypted_cb(output, type_, msg, msgtxt, 302 original_message, fjid, resource, jid, xhtml, 303 subject, chatstate, msg_id, composing_xep, label, 304 forward_from, delayed, session, form_node, 305 user_nick, keyID, callback) 306 gajim.thread_interface(encrypt_thread, [msg, keyID, False], 307 _on_encrypted, []) 308 return 309 310 self._message_encrypted_cb(('', error), type_, msg, msgtxt, 311 original_message, fjid, resource, jid, xhtml, subject, 312 chatstate, msg_id, composing_xep, label, forward_from, delayed, 313 session, form_node, user_nick, keyID, callback) 314 315 self._on_continue_message(type_, msg, msgtxt, original_message, fjid, 316 resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id, 317 composing_xep, label, forward_from, delayed, session, form_node, 318 user_nick, callback) 319
320 - def _message_encrypted_cb(self, output, type_, msg, msgtxt, 321 original_message, fjid, resource, jid, xhtml, subject, chatstate, msg_id, 322 composing_xep, label, forward_from, delayed, session, form_node, user_nick, 323 keyID, callback):
324 msgenc, error = output 325 326 if msgenc and not error: 327 msgtxt = '[This message is *encrypted* (See :XEP:`27`]' 328 lang = os.getenv('LANG') 329 if lang is not None and lang != 'en': # we're not english 330 # one in locale and one en 331 msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ 332 ' (' + msgtxt + ')' 333 self._on_continue_message(type_, msg, msgtxt, original_message, 334 fjid, resource, jid, xhtml, subject, msgenc, keyID, 335 chatstate, msg_id, composing_xep, label, forward_from, delayed, 336 session, form_node, user_nick, callback) 337 return 338 # Encryption failed, do not send message 339 tim = localtime() 340 self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
341
342 - def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid, 343 resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id, 344 composing_xep, label, forward_from, delayed, session, form_node, user_nick, 345 callback):
346 if type_ == 'chat': 347 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_, 348 xhtml=xhtml) 349 else: 350 if subject: 351 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', 352 subject=subject, xhtml=xhtml) 353 else: 354 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', 355 xhtml=xhtml) 356 357 if msg_id: 358 msg_iq.setID(msg_id) 359 360 if msgenc: 361 msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) 362 363 if form_node: 364 msg_iq.addChild(node=form_node) 365 if label: 366 msg_iq.addChild(node=label) 367 368 # XEP-0172: user_nickname 369 if user_nick: 370 msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData( 371 user_nick) 372 373 # TODO: We might want to write a function so we don't need to 374 # reproduce that ugly if somewhere else. 375 if resource: 376 contact = gajim.contacts.get_contact(self.name, jid, resource) 377 else: 378 contact = gajim.contacts.get_contact_with_highest_priority(self.name, 379 jid) 380 381 # chatstates - if peer supports xep85 or xep22, send chatstates 382 # please note that the only valid tag inside a message containing a <body> 383 # tag is the active event 384 if chatstate is not None and contact: 385 if ((composing_xep == 'XEP-0085' or not composing_xep) \ 386 and composing_xep != 'asked_once') or \ 387 contact.supports(common.xmpp.NS_CHATSTATES): 388 # XEP-0085 389 msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES) 390 if composing_xep in ('XEP-0022', 'asked_once') or \ 391 not composing_xep: 392 # XEP-0022 393 chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT) 394 if chatstate is 'composing' or msgtxt: 395 chatstate_node.addChild(name='composing') 396 397 if forward_from: 398 addresses = msg_iq.addChild('addresses', 399 namespace=common.xmpp.NS_ADDRESS) 400 addresses.addChild('address', attrs = {'type': 'ofrom', 401 'jid': forward_from}) 402 403 # XEP-0203 404 if delayed: 405 our_jid = gajim.get_jid_from_account(self.name) + '/' + \ 406 self.server_resource 407 timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed)) 408 msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2, 409 attrs={'from': our_jid, 'stamp': timestamp}) 410 411 # XEP-0184 412 if msgtxt and gajim.config.get_per('accounts', self.name, 413 'request_receipt') and contact and contact.supports( 414 common.xmpp.NS_RECEIPTS): 415 msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS) 416 417 if session: 418 # XEP-0201 419 session.last_send = time.time() 420 msg_iq.setThread(session.thread_id) 421 422 # XEP-0200 423 if session.enable_encryption: 424 msg_iq = session.encrypt_stanza(msg_iq) 425 426 if callback: 427 callback(jid, msg, keyID, forward_from, session, original_message, 428 subject, type_, msg_iq)
429
430 - def log_message(self, jid, msg, forward_from, session, original_message, 431 subject, type_):
432 if not forward_from and session and session.is_loggable(): 433 ji = gajim.get_jid_without_resource(jid) 434 if gajim.config.should_log(self.name, ji): 435 log_msg = msg 436 if original_message is not None: 437 log_msg = original_message 438 if subject: 439 log_msg = _('Subject: %(subject)s\n%(message)s') % \ 440 {'subject': subject, 'message': log_msg} 441 if log_msg: 442 if type_ == 'chat': 443 kind = 'chat_msg_sent' 444 else: 445 kind = 'single_msg_sent' 446 try: 447 gajim.logger.write(kind, jid, log_msg) 448 except exceptions.PysqliteOperationalError, e: 449 self.dispatch('DB_ERROR', (_('Disk Write Error'), 450 str(e))) 451 except exceptions.DatabaseMalformed: 452 pritext = _('Database Error') 453 sectext = _('The database file (%s) cannot be read. Try to ' 454 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)' 455 ' or remove it (all history will be lost).') % \ 456 common.logger.LOG_DB_PATH 457 self.dispatch('DB_ERROR', (pritext, sectext))
458
459 - def ack_subscribed(self, jid):
460 """ 461 To be implemented by derivated classes 462 """ 463 raise NotImplementedError
464
465 - def ack_unsubscribed(self, jid):
466 """ 467 To be implemented by derivated classes 468 """ 469 raise NotImplementedError
470
471 - def request_subscription(self, jid, msg='', name='', groups=[], 472 auto_auth=False):
473 """ 474 To be implemented by derivated classes 475 """ 476 raise NotImplementedError
477
478 - def send_authorization(self, jid):
479 """ 480 To be implemented by derivated classes 481 """ 482 raise NotImplementedError
483
484 - def refuse_authorization(self, jid):
485 """ 486 To be implemented by derivated classes 487 """ 488 raise NotImplementedError
489
490 - def unsubscribe(self, jid, remove_auth = True):
491 """ 492 To be implemented by derivated classes 493 """ 494 raise NotImplementedError
495
496 - def unsubscribe_agent(self, agent):
497 """ 498 To be implemented by derivated classes 499 """ 500 raise NotImplementedError
501
502 - def update_contact(self, jid, name, groups):
503 if self.connection: 504 self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
505
506 - def update_contacts(self, contacts):
507 """ 508 Update multiple roster items 509 """ 510 if self.connection: 511 self.connection.getRoster().setItemMulti(contacts)
512
513 - def new_account(self, name, config, sync=False):
514 """ 515 To be implemented by derivated classes 516 """ 517 raise NotImplementedError
518
519 - def _on_new_account(self, con=None, con_type=None):
520 """ 521 To be implemented by derivated classes 522 """ 523 raise NotImplementedError
524
525 - def account_changed(self, new_name):
526 self.name = new_name
527
528 - def request_last_status_time(self, jid, resource, groupchat_jid=None):
529 """ 530 groupchat_jid is used when we want to send a request to a real jid and 531 act as if the answer comes from the groupchat_jid 532 """ 533 if not gajim.account_is_connected(self.name): 534 return 535 to_whom_jid = jid 536 if resource: 537 to_whom_jid += '/' + resource 538 iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\ 539 common.xmpp.NS_LAST) 540 id_ = self.connection.getAnID() 541 iq.setID(id_) 542 if groupchat_jid: 543 self.groupchat_jids[id_] = groupchat_jid 544 self.last_ids.append(id_) 545 self.connection.send(iq)
546
547 - def request_os_info(self, jid, resource):
548 """ 549 To be implemented by derivated classes 550 """ 551 raise NotImplementedError
552
553 - def get_settings(self):
554 """ 555 To be implemented by derivated classes 556 """ 557 raise NotImplementedError
558
559 - def get_bookmarks(self):
560 """ 561 To be implemented by derivated classes 562 """ 563 raise NotImplementedError
564
565 - def store_bookmarks(self):
566 """ 567 To be implemented by derivated classes 568 """ 569 raise NotImplementedError
570
571 - def get_metacontacts(self):
572 """ 573 To be implemented by derivated classes 574 """ 575 raise NotImplementedError
576
577 - def send_agent_status(self, agent, ptype):
578 """ 579 To be implemented by derivated classes 580 """ 581 raise NotImplementedError
582
583 - def gpg_passphrase(self, passphrase):
584 if self.gpg: 585 use_gpg_agent = gajim.config.get('use_gpg_agent') 586 if use_gpg_agent: 587 self.gpg.passphrase = None 588 else: 589 self.gpg.passphrase = passphrase
590
591 - def ask_gpg_keys(self):
592 if self.gpg: 593 keys = self.gpg.get_keys() 594 return keys 595 return None
596
597 - def ask_gpg_secrete_keys(self):
598 if self.gpg: 599 keys = self.gpg.get_secret_keys() 600 return keys 601 return None
602
603 - def load_roster_from_db(self):
604 # Do nothing by default 605 return
606
607 - def _event_dispatcher(self, realm, event, data):
608 if realm == '': 609 if event == common.xmpp.transports_nb.DATA_RECEIVED: 610 self.dispatch('STANZA_ARRIVED', unicode(data, errors='ignore')) 611 elif event == common.xmpp.transports_nb.DATA_SENT: 612 self.dispatch('STANZA_SENT', unicode(data))
613
614 - def change_status(self, show, msg, auto=False):
615 if not msg: 616 msg = '' 617 sign_msg = False 618 if not auto and not show == 'offline': 619 sign_msg = True 620 if show != 'invisible': 621 # We save it only when privacy list is accepted 622 self.status = msg 623 if show != 'offline' and self.connected < 1: 624 # set old_show to requested 'show' in case we need to 625 # recconect before we auth to server 626 self.old_show = show 627 self.on_purpose = False 628 self.server_resource = self._compute_resource() 629 if gajim.HAVE_GPG: 630 self.USE_GPG = True 631 self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) 632 self.connect_and_init(show, msg, sign_msg) 633 return 634 635 if show == 'offline': 636 self.connected = 0 637 if self.connection: 638 p = common.xmpp.Presence(typ = 'unavailable') 639 p = self.add_sha(p, False) 640 if msg: 641 p.setStatus(msg) 642 643 self.connection.RegisterDisconnectHandler(self._on_disconnected) 644 self.connection.send(p, now=True) 645 self.connection.start_disconnect() 646 else: 647 self._on_disconnected() 648 return 649 650 if show != 'offline' and self.connected > 0: 651 # dont'try to connect, when we are in state 'connecting' 652 if self.connected == 1: 653 return 654 if show == 'invisible': 655 self._change_to_invisible(msg) 656 return 657 if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']: 658 return -1 659 was_invisible = self.connected == gajim.SHOW_LIST.index('invisible') 660 self.connected = gajim.SHOW_LIST.index(show) 661 if was_invisible: 662 self._change_from_invisible() 663 self._update_status(show, msg)
664
665 -class Connection(CommonConnection, ConnectionHandlers):
666 - def __init__(self, name):
667 CommonConnection.__init__(self, name) 668 ConnectionHandlers.__init__(self) 669 # this property is used to prevent double connections 670 self.last_connection = None # last ClientCommon instance 671 # If we succeed to connect, remember it so next time we try (after a 672 # disconnection) we try only this type. 673 self.last_connection_type = None 674 self.lang = None 675 if locale.getdefaultlocale()[0]: 676 self.lang = locale.getdefaultlocale()[0].split('_')[0] 677 # increase/decrease default timeout for server responses 678 self.try_connecting_for_foo_secs = 45 679 # holds the actual hostname to which we are connected 680 self.connected_hostname = None 681 self.last_time_to_reconnect = None 682 self.new_account_info = None 683 self.new_account_form = None 684 self.annotations = {} 685 self.last_io = gajim.idlequeue.current_time() 686 self.last_sent = [] 687 self.last_history_time = {} 688 self.password = passwords.get_password(name) 689 690 self.music_track_info = 0 691 self.location_info = {} 692 self.pubsub_supported = False 693 self.pubsub_publish_options_supported = False 694 # Do we auto accept insecure connection 695 self.connection_auto_accepted = False 696 self.pasword_callback = None 697 698 self.on_connect_success = None 699 self.on_connect_failure = None 700 self.retrycount = 0 701 self.jids_for_auto_auth = [] # list of jid to auto-authorize 702 self.available_transports = {} # list of available transports on this 703 # server {'icq': ['icq.server.com', 'icq2.server.com'], } 704 self.private_storage_supported = True 705 self.streamError = '' 706 self.secret_hmac = str(random.random())[2:]
707 # END __init__ 708
710 if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'): 711 self.keepalives = gajim.config.get_per('accounts', self.name, 712 'keep_alive_every_foo_secs') 713 else: 714 self.keepalives = 0 715 if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'): 716 self.pingalives = gajim.config.get_per('accounts', self.name, 717 'ping_alive_every_foo_secs') 718 else: 719 self.pingalives = 0 720 self.client_cert = gajim.config.get_per('accounts', self.name, 721 'client_cert')
722
723 - def check_jid(self, jid):
724 return helpers.parse_jid(jid)
725
726 - def _reconnect(self):
727 # Do not try to reco while we are already trying 728 self.time_to_reconnect = None 729 if self.connected < 2: # connection failed 730 log.debug('reconnect') 731 self.connected = 1 732 self.dispatch('STATUS', 'connecting') 733 self.retrycount += 1 734 self.on_connect_auth = self._discover_server_at_connection 735 self.connect_and_init(self.old_show, self.status, self.USE_GPG) 736 else: 737 # reconnect succeeded 738 self.time_to_reconnect = None 739 self.retrycount = 0
740 741 # We are doing disconnect at so many places, better use one function in all
742 - def disconnect(self, on_purpose=False):
743 gajim.interface.music_track_changed(None, None, self.name) 744 self.reset_awaiting_pep() 745 self.on_purpose = on_purpose 746 self.connected = 0 747 self.time_to_reconnect = None 748 self.privacy_rules_supported = False 749 if self.connection: 750 # make sure previous connection is completely closed 751 gajim.proxy65_manager.disconnect(self.connection) 752 self.terminate_sessions() 753 self.remove_all_transfers() 754 self.connection.disconnect() 755 self.last_connection = None 756 self.connection = None
757
758 - def _disconnectedReconnCB(self):
759 """ 760 Called when we are disconnected 761 """ 762 log.info('disconnectedReconnCB called') 763 if gajim.account_is_connected(self.name): 764 # we cannot change our status to offline or connecting 765 # after we auth to server 766 self.old_show = gajim.SHOW_LIST[self.connected] 767 self.connected = 0 768 if not self.on_purpose: 769 self.dispatch('STATUS', 'offline') 770 self.disconnect() 771 if gajim.config.get_per('accounts', self.name, 'autoreconnect'): 772 self.connected = -1 773 self.dispatch('STATUS', 'error') 774 if gajim.status_before_autoaway[self.name]: 775 # We were auto away. So go back online 776 self.status = gajim.status_before_autoaway[self.name] 777 gajim.status_before_autoaway[self.name] = '' 778 self.old_show = 'online' 779 # this check has moved from _reconnect method 780 # do exponential backoff until 15 minutes, 781 # then small linear increase 782 if self.retrycount < 2 or self.last_time_to_reconnect is None: 783 self.last_time_to_reconnect = 5 784 if self.last_time_to_reconnect < 800: 785 self.last_time_to_reconnect *= 1.5 786 self.last_time_to_reconnect += randomsource.randint(0, 5) 787 self.time_to_reconnect = int(self.last_time_to_reconnect) 788 log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect) 789 gajim.idlequeue.set_alarm(self._reconnect_alarm, 790 self.time_to_reconnect) 791 elif self.on_connect_failure: 792 self.on_connect_failure() 793 self.on_connect_failure = None 794 else: 795 # show error dialog 796 self._connection_lost() 797 else: 798 self.disconnect() 799 self.on_purpose = False
800 # END disconnectedReconnCB 801
802 - def _connection_lost(self):
803 log.debug('_connection_lost') 804 self.disconnect(on_purpose = False) 805 self.dispatch('STATUS', 'offline') 806 self.dispatch('CONNECTION_LOST', 807 (_('Connection with account "%s" has been lost') % self.name, 808 _('Reconnect manually.')))
809
810 - def _event_dispatcher(self, realm, event, data):
811 CommonConnection._event_dispatcher(self, realm, event, data) 812 if realm == common.xmpp.NS_REGISTER: 813 if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED: 814 # data is (agent, DataFrom, is_form, error_msg) 815 if self.new_account_info and \ 816 self.new_account_info['hostname'] == data[0]: 817 # it's a new account 818 if not data[1]: # wrong answer 819 self.dispatch('ACC_NOT_OK', ( 820 _('Server %(name)s answered wrongly to register request: ' 821 '%(error)s') % {'name': data[0], 'error': data[3]})) 822 return 823 is_form = data[2] 824 conf = data[1] 825 if self.new_account_form: 826 def _on_register_result(result): 827 if not common.xmpp.isResultNode(result): 828 self.dispatch('ACC_NOT_OK', (result.getError())) 829 return 830 if gajim.HAVE_GPG: 831 self.USE_GPG = True 832 self.gpg = GnuPG.GnuPG(gajim.config.get( 833 'use_gpg_agent')) 834 self.dispatch('ACC_OK', (self.new_account_info)) 835 self.new_account_info = None 836 self.new_account_form = None 837 if self.connection: 838 self.connection.UnregisterDisconnectHandler( 839 self._on_new_account) 840 self.disconnect(on_purpose=True)
841 # it's the second time we get the form, we have info user 842 # typed, so send them 843 if is_form: 844 #TODO: Check if form has changed 845 iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname) 846 iq.setTag('query').addChild(node=self.new_account_form) 847 self.connection.SendAndCallForResponse(iq, 848 _on_register_result) 849 else: 850 if self.new_account_form.keys().sort() != \ 851 conf.keys().sort(): 852 # requested config has changed since first connection 853 self.dispatch('ACC_NOT_OK', (_( 854 'Server %s provided a different registration form')\ 855 % data[0])) 856 return 857 common.xmpp.features_nb.register(self.connection, 858 self._hostname, self.new_account_form, 859 _on_register_result) 860 return 861 try: 862 errnum = self.connection.Connection.ssl_errnum 863 except AttributeError: 864 errnum = -1 # we don't have an errnum 865 ssl_msg = '' 866 if errnum > 0: 867 ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum) 868 ssl_cert = '' 869 if hasattr(self.connection.Connection, 'ssl_cert_pem'): 870 ssl_cert = self.connection.Connection.ssl_cert_pem 871 ssl_fingerprint = '' 872 if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'): 873 ssl_fingerprint = \ 874 self.connection.Connection.ssl_fingerprint_sha1 875 self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg, 876 errnum, ssl_cert, ssl_fingerprint)) 877 self.connection.UnregisterDisconnectHandler( 878 self._on_new_account) 879 self.disconnect(on_purpose=True) 880 return 881 if not data[1]: # wrong answer 882 self.dispatch('ERROR', (_('Invalid answer'), 883 _('Transport %(name)s answered wrongly to register request: ' 884 '%(error)s') % {'name': data[0], 'error': data[3]})) 885 return 886 is_form = data[2] 887 conf = data[1] 888 self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form)) 889 elif realm == common.xmpp.NS_PRIVACY: 890 if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED: 891 # data is (list) 892 self.dispatch('PRIVACY_LISTS_RECEIVED', (data)) 893 elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED: 894 # data is (resp) 895 if not data: 896 return 897 rules = [] 898 name = data.getTag('query').getTag('list').getAttr('name') 899 for child in data.getTag('query').getTag('list').getChildren(): 900 dict_item = child.getAttrs() 901 childs = [] 902 if 'type' in dict_item: 903 for scnd_child in child.getChildren(): 904 childs += [scnd_child.getName()] 905 rules.append({'action':dict_item['action'], 906 'type':dict_item['type'], 'order':dict_item['order'], 907 'value':dict_item['value'], 'child':childs}) 908 else: 909 for scnd_child in child.getChildren(): 910 childs.append(scnd_child.getName()) 911 rules.append({'action':dict_item['action'], 912 'order':dict_item['order'], 'child':childs}) 913 self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules)) 914 elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT: 915 # data is (dict) 916 self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
917
918 - def _select_next_host(self, hosts):
919 """ 920 Selects the next host according to RFC2782 p.3 based on it's priority. 921 Chooses between hosts with the same priority randomly, where the 922 probability of being selected is proportional to the weight of the host 923 """ 924 hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio')) 925 926 try: 927 lowest_prio = hosts_by_prio[0]['prio'] 928 except IndexError: 929 raise ValueError("No hosts to choose from!") 930 931 hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio] 932 933 if len(hosts_lowest_prio) == 1: 934 return hosts_lowest_prio[0] 935 else: 936 rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio)) 937 weightsum = 0 938 for host in sorted(hosts_lowest_prio, key=operator.itemgetter( 939 'weight')): 940 weightsum += host['weight'] 941 if weightsum >= rndint: 942 return host
943
944 - def connect(self, data = None):
945 """ 946 Start a connection to the Jabber server 947 948 Returns connection, and connection type ('tls', 'ssl', 'plain', '') data 949 MUST contain hostname, usessl, proxy, use_custom_host, custom_host (if 950 use_custom_host), custom_port (if use_custom_host) 951 """ 952 if self.connection: 953 return self.connection, '' 954 955 if data: 956 hostname = data['hostname'] 957 self.try_connecting_for_foo_secs = 45 958 p = data['proxy'] 959 use_srv = True 960 use_custom = data['use_custom_host'] 961 if use_custom: 962 custom_h = data['custom_host'] 963 custom_p = data['custom_port'] 964 else: 965 hostname = gajim.config.get_per('accounts', self.name, 'hostname') 966 usessl = gajim.config.get_per('accounts', self.name, 'usessl') 967 self.try_connecting_for_foo_secs = gajim.config.get_per('accounts', 968 self.name, 'try_connecting_for_foo_secs') 969 p = gajim.config.get_per('accounts', self.name, 'proxy') 970 use_srv = gajim.config.get_per('accounts', self.name, 'use_srv') 971 use_custom = gajim.config.get_per('accounts', self.name, 972 'use_custom_host') 973 custom_h = gajim.config.get_per('accounts', self.name, 'custom_host') 974 custom_p = gajim.config.get_per('accounts', self.name, 'custom_port') 975 976 # create connection if it doesn't already exist 977 self.connected = 1 978 if p and p in gajim.config.get_per('proxies'): 979 proxy = {} 980 proxyptr = gajim.config.get_per('proxies', p) 981 for key in proxyptr.keys(): 982 proxy[key] = proxyptr[key][1] 983 984 elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'): 985 try: 986 try: 987 env_http_proxy = os.environ['HTTP_PROXY'] 988 except Exception: 989 env_http_proxy = os.environ['http_proxy'] 990 env_http_proxy = env_http_proxy.strip('"') 991 # Dispose of the http:// prefix 992 env_http_proxy = env_http_proxy.split('://') 993 env_http_proxy = env_http_proxy[len(env_http_proxy)-1] 994 env_http_proxy = env_http_proxy.split('@') 995 996 if len(env_http_proxy) == 2: 997 login = env_http_proxy[0].split(':') 998 addr = env_http_proxy[1].split(':') 999 else: 1000 login = ['', ''] 1001 addr = env_http_proxy[0].split(':') 1002 1003 proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]} 1004 1005 if len(addr) == 2: 1006 proxy['port'] = addr[1] 1007 else: 1008 proxy['port'] = 3128 1009 1010 if len(login) == 2: 1011 proxy['pass'] = login[1] 1012 proxy['useauth'] = True 1013 else: 1014 proxy['pass'] = u'' 1015 1016 except Exception: 1017 proxy = None 1018 else: 1019 proxy = None 1020 h = hostname 1021 p = 5222 1022 ssl_p = 5223 1023 # use_srv = False # wants ssl? disable srv lookup 1024 if use_custom: 1025 h = custom_h 1026 p = custom_p 1027 ssl_p = custom_p 1028 use_srv = False 1029 1030 # SRV resolver 1031 self._proxy = proxy 1032 self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10, 1033 'weight': 10} ] 1034 self._hostname = hostname 1035 if use_srv: 1036 # add request for srv query to the resolve, on result '_on_resolve' 1037 # will be called 1038 gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h), 1039 self._on_resolve) 1040 else: 1041 self._on_resolve('', [])
1042
1043 - def _on_resolve(self, host, result_array):
1044 # SRV query returned at least one valid result, we put it in hosts dict 1045 if len(result_array) != 0: 1046 self._hosts = [i for i in result_array] 1047 # Add ssl port 1048 ssl_p = 5223 1049 if gajim.config.get_per('accounts', self.name, 'use_custom_host'): 1050 ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port') 1051 for i in self._hosts: 1052 i['ssl_port'] = ssl_p 1053 self._connect_to_next_host()
1054 1055
1056 - def _connect_to_next_host(self, retry=False):
1057 log.debug('Connection to next host') 1058 if len(self._hosts): 1059 # No config option exist when creating a new account 1060 if self.last_connection_type: 1061 self._connection_types = [self.last_connection_type] 1062 elif self.name in gajim.config.get_per('accounts'): 1063 self._connection_types = gajim.config.get_per('accounts', self.name, 1064 'connection_types').split() 1065 else: 1066 self._connection_types = ['tls', 'ssl', 'plain'] 1067 1068 if self._proxy and self._proxy['type']=='bosh': 1069 # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain" 1070 # connection and TLS with handshake right after TCP connecting ("ssl") 1071 scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0] 1072 if scheme=='https': 1073 self._connection_types = ['ssl'] 1074 else: 1075 self._connection_types = ['plain'] 1076 1077 host = self._select_next_host(self._hosts) 1078 self._current_host = host 1079 self._hosts.remove(host) 1080 self.connect_to_next_type() 1081 1082 else: 1083 if not retry and self.retrycount == 0: 1084 log.debug("Out of hosts, giving up connecting to %s", self.name) 1085 self.time_to_reconnect = None 1086 if self.on_connect_failure: 1087 self.on_connect_failure() 1088 self.on_connect_failure = None 1089 else: 1090 # shown error dialog 1091 self._connection_lost() 1092 else: 1093 # try reconnect if connection has failed before auth to server 1094 self._disconnectedReconnCB()
1095
1096 - def connect_to_next_type(self, retry=False):
1097 if len(self._connection_types): 1098 self._current_type = self._connection_types.pop(0) 1099 if self.last_connection: 1100 self.last_connection.socket.disconnect() 1101 self.last_connection = None 1102 self.connection = None 1103 1104 if self._current_type == 'ssl': 1105 # SSL (force TLS on different port than plain) 1106 # If we do TLS over BOSH, port of XMPP server should be the standard one 1107 # and TLS should be negotiated because TLS on 5223 is deprecated 1108 if self._proxy and self._proxy['type']=='bosh': 1109 port = self._current_host['port'] 1110 else: 1111 port = self._current_host['ssl_port'] 1112 elif self._current_type == 'tls': 1113 # TLS - negotiate tls after XMPP stream is estabilished 1114 port = self._current_host['port'] 1115 elif self._current_type == 'plain': 1116 # plain connection on defined port 1117 port = self._current_host['port'] 1118 1119 cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem') 1120 mycerts = common.gajim.MY_CACERTS 1121 secure_tuple = (self._current_type, cacerts, mycerts) 1122 1123 con = common.xmpp.NonBlockingClient( 1124 domain=self._hostname, 1125 caller=self, 1126 idlequeue=gajim.idlequeue) 1127 1128 self.last_connection = con 1129 # increase default timeout for server responses 1130 common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs 1131 # FIXME: this is a hack; need a better way 1132 if self.on_connect_success == self._on_new_account: 1133 con.RegisterDisconnectHandler(self._on_new_account) 1134 1135 self.log_hosttype_info(port) 1136 con.connect( 1137 hostname=self._current_host['host'], 1138 port=port, 1139 on_connect=self.on_connect_success, 1140 on_proxy_failure=self.on_proxy_failure, 1141 on_connect_failure=self.connect_to_next_type, 1142 proxy=self._proxy, 1143 secure_tuple = secure_tuple) 1144 else: 1145 self._connect_to_next_host(retry)
1146
1147 - def log_hosttype_info(self, port):
1148 msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name, 1149 self._current_host['host'], port, self._current_type) 1150 log.info(msg) 1151 if self._proxy: 1152 msg = '>>>>>> ' 1153 if self._proxy['type']=='bosh': 1154 msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri']) 1155 if self._proxy['type'] in ['http', 'socks5'] or self._proxy['bosh_useproxy']: 1156 msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port']) 1157 log.info(msg)
1158
1159 - def _connect_failure(self, con_type=None):
1160 if not con_type: 1161 # we are not retrying, and not conecting 1162 if not self.retrycount and self.connected != 0: 1163 self.disconnect(on_purpose = True) 1164 self.dispatch('STATUS', 'offline') 1165 pritxt = _('Could not connect to "%s"') % self._hostname 1166 sectxt = _('Check your connection or try again later.') 1167 if self.streamError: 1168 # show error dialog 1169 key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError 1170 if key in common.xmpp.ERRORS: 1171 sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2] 1172 self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt))) 1173 return 1174 # show popup 1175 self.dispatch('CONNECTION_LOST', (pritxt, sectxt))
1176
1177 - def on_proxy_failure(self, reason):
1178 log.error('Connection to proxy failed: %s' % reason) 1179 self.time_to_reconnect = None 1180 self.on_connect_failure = None 1181 self.disconnect(on_purpose = True) 1182 self.dispatch('STATUS', 'offline') 1183 self.dispatch('CONNECTION_LOST', 1184 (_('Connection to proxy failed'), reason))
1185
1186 - def _connect_success(self, con, con_type):
1187 if not self.connected: # We went offline during connecting process 1188 # FIXME - not possible, maybe it was when we used threads 1189 return 1190 _con_type = con_type 1191 if _con_type != self._current_type: 1192 log.info('Connecting to next type beacuse desired is %s and returned is %s' 1193 % (self._current_type, _con_type)) 1194 self.connect_to_next_type() 1195 return 1196 con.RegisterDisconnectHandler(self._on_disconnected) 1197 if _con_type == 'plain' and gajim.config.get_per('accounts', self.name, 1198 'warn_when_plaintext_connection'): 1199 self.dispatch('PLAIN_CONNECTION', (con,)) 1200 return True 1201 if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \ 1202 and gajim.config.get_per('accounts', self.name, 1203 'warn_when_insecure_ssl_connection') and \ 1204 not self.connection_auto_accepted: 1205 # Pyopenssl is not used 1206 self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type)) 1207 return True 1208 return self.connection_accepted(con, con_type)
1209
1210 - def connection_accepted(self, con, con_type):
1211 if not con or not con.Connection: 1212 self.disconnect(on_purpose=True) 1213 self.dispatch('STATUS', 'offline') 1214 self.dispatch('CONNECTION_LOST', 1215 (_('Could not connect to account %s') % self.name, 1216 _('Connection with account %s has been lost. Retry connecting.') % \ 1217 self.name)) 1218 return 1219 self.hosts = [] 1220 self.connection_auto_accepted = False 1221 self.connected_hostname = self._current_host['host'] 1222 self.on_connect_failure = None 1223 con.UnregisterDisconnectHandler(self._on_disconnected) 1224 con.RegisterDisconnectHandler(self._disconnectedReconnCB) 1225 log.debug('Connected to server %s:%s with %s' % ( 1226 self._current_host['host'], self._current_host['port'], con_type)) 1227 1228 self.last_connection_type = con_type 1229 if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): 1230 name = None 1231 else: 1232 name = gajim.config.get_per('accounts', self.name, 'name') 1233 hostname = gajim.config.get_per('accounts', self.name, 'hostname') 1234 self.connection = con 1235 try: 1236 errnum = con.Connection.ssl_errnum 1237 except AttributeError: 1238 errnum = -1 # we don't have an errnum 1239 if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts', 1240 self.name, 'ignore_ssl_errors'): 1241 text = _('The authenticity of the %s certificate could be invalid.') %\ 1242 hostname 1243 if errnum in ssl_error: 1244 text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum] 1245 else: 1246 text += _('\nUnknown SSL error: %d') % errnum 1247 self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem, 1248 con.Connection.ssl_fingerprint_sha1)) 1249 return True 1250 if hasattr(con.Connection, 'ssl_fingerprint_sha1'): 1251 saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1') 1252 if saved_fingerprint: 1253 # Check sha1 fingerprint 1254 if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint: 1255 self.dispatch('FINGERPRINT_ERROR', 1256 (con.Connection.ssl_fingerprint_sha1,)) 1257 return True 1258 else: 1259 gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1', 1260 con.Connection.ssl_fingerprint_sha1) 1261 self._register_handlers(con, con_type) 1262 con.auth( 1263 user=name, 1264 password=self.password, 1265 resource=self.server_resource, 1266 sasl=1, 1267 on_auth=self.__on_auth)
1268
1269 - def ssl_certificate_accepted(self):
1270 if not self.connection: 1271 self.disconnect(on_purpose=True) 1272 self.dispatch('STATUS', 'offline') 1273 self.dispatch('CONNECTION_LOST', 1274 (_('Could not connect to account %s') % self.name, 1275 _('Connection with account %s has been lost. Retry connecting.') % \ 1276 self.name)) 1277 return 1278 name = gajim.config.get_per('accounts', self.name, 'name') 1279 self._register_handlers(self.connection, 'ssl') 1280 self.connection.auth(name, self.password, self.server_resource, 1, 1281 self.__on_auth)
1282
1283 - def _register_handlers(self, con, con_type):
1284 self.peerhost = con.get_peerhost() 1285 # notify the gui about con_type 1286 self.dispatch('CON_TYPE', con_type) 1287 ConnectionHandlers._register_handlers(self, con, con_type)
1288
1289 - def __on_auth(self, con, auth):
1290 if not con: 1291 self.disconnect(on_purpose=True) 1292 self.dispatch('STATUS', 'offline') 1293 self.dispatch('CONNECTION_LOST', 1294 (_('Could not connect to "%s"') % self._hostname, 1295 _('Check your connection or try again later'))) 1296 if self.on_connect_auth: 1297 self.on_connect_auth(None) 1298 self.on_connect_auth = None 1299 return 1300 if not self.connected: # We went offline during connecting process 1301 if self.on_connect_auth: 1302 self.on_connect_auth(None) 1303 self.on_connect_auth = None 1304 return 1305 if hasattr(con, 'Resource'): 1306 self.server_resource = con.Resource 1307 if gajim.config.get_per('accounts', self.name, 'anonymous_auth'): 1308 # Get jid given by server 1309 old_jid = gajim.get_jid_from_account(self.name) 1310 gajim.config.set_per('accounts', self.name, 'name', con.User) 1311 new_jid = gajim.get_jid_from_account(self.name) 1312 self.dispatch('NEW_JID', (old_jid, new_jid)) 1313 if auth: 1314 self.last_io = gajim.idlequeue.current_time() 1315 self.connected = 2 1316 self.retrycount = 0 1317 if self.on_connect_auth: 1318 self.on_connect_auth(con) 1319 self.on_connect_auth = None 1320 else: 1321 gajim.log.debug("Couldn't authenticate to %s" % self._hostname) 1322 self.disconnect(on_purpose = True) 1323 self.dispatch('STATUS', 'offline') 1324 self.dispatch('ERROR', (_('Authentication failed with "%s"') % \ 1325 self._hostname, 1326 _('Please check your login and password for correctness.'))) 1327 if self.on_connect_auth: 1328 self.on_connect_auth(None) 1329 self.on_connect_auth = None
1330 # END connect 1331
1332 - def add_lang(self, stanza):
1333 if self.lang: 1334 stanza.setAttr('xml:lang', self.lang)
1335
1336 - def get_privacy_lists(self):
1337 if not gajim.account_is_connected(self.name): 1338 return 1339 common.xmpp.features_nb.getPrivacyLists(self.connection)
1340
1341 - def send_keepalive(self):
1342 # nothing received for the last foo seconds 1343 if self.connection: 1344 self.connection.send(' ')
1345
1346 - def _on_xmpp_ping_answer(self, iq_obj):
1347 id_ = unicode(iq_obj.getAttr('id')) 1348 assert id_ == self.awaiting_xmpp_ping_id 1349 self.awaiting_xmpp_ping_id = None
1350
1351 - def sendPing(self, pingTo=None):
1352 """ 1353 Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to 1354 server to detect connection failure at application level 1355 """ 1356 if not gajim.account_is_connected(self.name): 1357 return 1358 id_ = self.connection.getAnID() 1359 if pingTo: 1360 to = pingTo.get_full_jid() 1361 self.dispatch('PING_SENT', (pingTo)) 1362 else: 1363 to = gajim.config.get_per('accounts', self.name, 'hostname') 1364 self.awaiting_xmpp_ping_id = id_ 1365 iq = common.xmpp.Iq('get', to=to) 1366 iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING) 1367 iq.setID(id_) 1368 def _on_response(resp): 1369 timePong = time_time() 1370 if not common.xmpp.isResultNode(resp): 1371 self.dispatch('PING_ERROR', (pingTo)) 1372 return 1373 timeDiff = round(timePong - timePing, 2) 1374 self.dispatch('PING_REPLY', (pingTo, timeDiff))
1375 if pingTo: 1376 timePing = time_time() 1377 self.connection.SendAndCallForResponse(iq, _on_response) 1378 else: 1379 self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer) 1380 gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per( 1381 'accounts', self.name, 'time_for_ping_alive_answer')) 1382
1383 - def get_active_default_lists(self):
1384 if not gajim.account_is_connected(self.name): 1385 return 1386 common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
1387
1388 - def del_privacy_list(self, privacy_list):
1389 if not gajim.account_is_connected(self.name): 1390 return 1391 def _on_del_privacy_list_result(result): 1392 if result: 1393 self.dispatch('PRIVACY_LIST_REMOVED', privacy_list) 1394 else: 1395 self.dispatch('ERROR', (_('Error while removing privacy list'), 1396 _('Privacy list %s has not been removed. It is maybe active in ' 1397 'one of your connected resources. Deactivate it and try ' 1398 'again.') % privacy_list))
1399 common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list, 1400 _on_del_privacy_list_result) 1401
1402 - def get_privacy_list(self, title):
1403 if not gajim.account_is_connected(self.name): 1404 return 1405 common.xmpp.features_nb.getPrivacyList(self.connection, title)
1406
1407 - def set_privacy_list(self, listname, tags):
1408 if not gajim.account_is_connected(self.name): 1409 return 1410 common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags)
1411
1412 - def set_active_list(self, listname):
1413 if not gajim.account_is_connected(self.name): 1414 return 1415 common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active')
1416
1417 - def set_default_list(self, listname):
1418 if not gajim.account_is_connected(self.name): 1419 return 1420 common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
1421
1422 - def build_privacy_rule(self, name, action, order=1):
1423 """ 1424 Build a Privacy rule stanza for invisibility 1425 """ 1426 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') 1427 l = iq.getTag('query').setTag('list', {'name': name}) 1428 i = l.setTag('item', {'action': action, 'order': str(order)}) 1429 i.setTag('presence-out') 1430 return iq
1431
1432 - def build_invisible_rule(self):
1433 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') 1434 l = iq.getTag('query').setTag('list', {'name': 'invisible'}) 1435 if self.name in gajim.interface.status_sent_to_groups and \ 1436 len(gajim.interface.status_sent_to_groups[self.name]) > 0: 1437 for group in gajim.interface.status_sent_to_groups[self.name]: 1438 i = l.setTag('item', {'type': 'group', 'value': group, 1439 'action': 'allow', 'order': '1'}) 1440 i.setTag('presence-out') 1441 if self.name in gajim.interface.status_sent_to_users and \ 1442 len(gajim.interface.status_sent_to_users[self.name]) > 0: 1443 for jid in gajim.interface.status_sent_to_users[self.name]: 1444 i = l.setTag('item', {'type': 'jid', 'value': jid, 1445 'action': 'allow', 'order': '2'}) 1446 i.setTag('presence-out') 1447 i = l.setTag('item', {'action': 'deny', 'order': '3'}) 1448 i.setTag('presence-out') 1449 return iq
1450
1451 - def set_invisible_rule(self):
1452 if not gajim.account_is_connected(self.name): 1453 return 1454 iq = self.build_invisible_rule() 1455 self.connection.send(iq)
1456
1457 - def activate_privacy_rule(self, name):
1458 """ 1459 Activate a privacy rule 1460 """ 1461 if not gajim.account_is_connected(self.name): 1462 return 1463 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '') 1464 iq.getTag('query').setTag('active', {'name': name}) 1465 self.connection.send(iq)
1466
1467 - def send_invisible_presence(self, msg, signed, initial = False):
1468 if not gajim.account_is_connected(self.name): 1469 return 1470 if not self.privacy_rules_supported: 1471 self.dispatch('STATUS', gajim.SHOW_LIST[self.connected]) 1472 self.dispatch('ERROR', (_('Invisibility not supported'), 1473 _('Account %s doesn\'t support invisibility.') % self.name)) 1474 return 1475 # If we are already connected, and privacy rules are supported, send 1476 # offline presence first as it's required by XEP-0126 1477 if self.connected > 1 and self.privacy_rules_supported: 1478 self.on_purpose = True 1479 p = common.xmpp.Presence(typ = 'unavailable') 1480 p = self.add_sha(p, False) 1481 if msg: 1482 p.setStatus(msg) 1483 self.remove_all_transfers() 1484 self.connection.send(p) 1485 1486 # try to set the privacy rule 1487 iq = self.build_invisible_rule() 1488 self.connection.SendAndCallForResponse(iq, self._continue_invisible, 1489 {'msg': msg, 'signed': signed, 'initial': initial})
1490
1491 - def _continue_invisible(self, con, iq_obj, msg, signed, initial):
1492 if iq_obj.getType() == 'error': # server doesn't support privacy lists 1493 return 1494 # active the privacy rule 1495 self.privacy_rules_supported = True 1496 self.activate_privacy_rule('invisible') 1497 self.connected = gajim.SHOW_LIST.index('invisible') 1498 self.status = msg 1499 priority = unicode(gajim.get_priority(self.name, 'invisible')) 1500 p = common.xmpp.Presence(priority = priority) 1501 p = self.add_sha(p, True) 1502 if msg: 1503 p.setStatus(msg) 1504 if signed: 1505 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) 1506 self.connection.send(p) 1507 self.priority = priority 1508 self.dispatch('STATUS', 'invisible') 1509 if initial: 1510 # ask our VCard 1511 self.request_vcard(None) 1512 1513 # Get bookmarks from private namespace 1514 self.get_bookmarks() 1515 1516 # Get annotations 1517 self.get_annotations() 1518 1519 # Inform GUI we just signed in 1520 self.dispatch('SIGNED_IN', ())
1521
1522 - def get_signed_presence(self, msg, callback = None):
1523 if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'): 1524 return self.get_signed_msg(msg, callback) 1525 return ''
1526
1527 - def connect_and_auth(self):
1528 self.on_connect_success = self._connect_success 1529 self.on_connect_failure = self._connect_failure 1530 self.connect()
1531
1532 - def connect_and_init(self, show, msg, sign_msg):
1533 self.continue_connect_info = [show, msg, sign_msg] 1534 self.on_connect_auth = self._discover_server_at_connection 1535 self.connect_and_auth()
1536
1537 - def _discover_server_at_connection(self, con):
1538 self.connection = con 1539 if not gajim.account_is_connected(self.name): 1540 return 1541 self.connection.set_send_timeout(self.keepalives, self.send_keepalive) 1542 self.connection.set_send_timeout2(self.pingalives, self.sendPing) 1543 self.connection.onreceive(None) 1544 self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'), 1545 id_prefix='Gajim_') 1546 self.privacy_rules_requested = False 1547 # Discover Stun server(s) 1548 gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii( 1549 self.connected_hostname), self._on_stun_resolved)
1550
1551 - def _on_stun_resolved(self, host, result_array):
1552 if len(result_array) != 0: 1553 self._stun_servers = self._hosts = [i for i in result_array]
1554
1555 - def _request_privacy(self):
1556 iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '') 1557 id_ = self.connection.getAnID() 1558 iq.setID(id_) 1559 self.awaiting_answers[id_] = (PRIVACY_ARRIVED, ) 1560 self.connection.send(iq)
1561
1562 - def send_custom_status(self, show, msg, jid):
1563 if not show in gajim.SHOW_LIST: 1564 return -1 1565 if not gajim.account_is_connected(self.name): 1566 return 1567 sshow = helpers.get_xmpp_show(show) 1568 if not msg: 1569 msg = '' 1570 if show == 'offline': 1571 p = common.xmpp.Presence(typ = 'unavailable', to = jid) 1572 p = self.add_sha(p, False) 1573 if msg: 1574 p.setStatus(msg) 1575 else: 1576 signed = self.get_signed_presence(msg) 1577 priority = unicode(gajim.get_priority(self.name, sshow)) 1578 p = common.xmpp.Presence(typ = None, priority = priority, show = sshow, 1579 to = jid) 1580 p = self.add_sha(p) 1581 if msg: 1582 p.setStatus(msg) 1583 if signed: 1584 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) 1585 self.connection.send(p)
1586
1587 - def _change_to_invisible(self, msg):
1588 signed = self.get_signed_presence(msg) 1589 self.send_invisible_presence(msg, signed)
1590
1591 - def _change_from_invisible(self):
1592 if self.privacy_rules_supported: 1593 iq = self.build_privacy_rule('visible', 'allow') 1594 self.connection.send(iq) 1595 self.activate_privacy_rule('visible')
1596
1597 - def _update_status(self, show, msg):
1598 xmpp_show = helpers.get_xmpp_show(show) 1599 priority = unicode(gajim.get_priority(self.name, xmpp_show)) 1600 p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show) 1601 p = self.add_sha(p) 1602 if msg: 1603 p.setStatus(msg) 1604 signed = self.get_signed_presence(msg) 1605 if signed: 1606 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) 1607 if self.connection: 1608 self.connection.send(p) 1609 self.priority = priority 1610 self.dispatch('STATUS', show)
1611
1612 - def send_motd(self, jid, subject = '', msg = '', xhtml = None):
1613 if not gajim.account_is_connected(self.name): 1614 return 1615 msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject, 1616 xhtml = xhtml) 1617 1618 self.connection.send(msg_iq)
1619
1620 - def send_message(self, jid, msg, keyID=None, type_='chat', subject='', 1621 chatstate=None, msg_id=None, composing_xep=None, resource=None, 1622 user_nick=None, xhtml=None, label=None, session=None, forward_from=None, 1623 form_node=None, original_message=None, delayed=None, callback=None, 1624 callback_args=[], now=False):
1625 1626 def cb(jid, msg, keyID, forward_from, session, original_message, 1627 subject, type_, msg_iq): 1628 msg_id = self.connection.send(msg_iq, now=now) 1629 jid = helpers.parse_jid(jid) 1630 self.dispatch('MSGSENT', (jid, msg, keyID)) 1631 if callback: 1632 callback(msg_id, *callback_args) 1633 1634 self.log_message(jid, msg, forward_from, session, original_message, 1635 subject, type_)
1636 1637 self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, 1638 chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, 1639 resource=resource, user_nick=user_nick, xhtml=xhtml, label=label, 1640 session=session, forward_from=forward_from, form_node=form_node, 1641 original_message=original_message, delayed=delayed, callback=cb) 1642
1643 - def send_contacts(self, contacts, jid):
1644 """ 1645 Send contacts with RosterX (Xep-0144) 1646 """ 1647 if not gajim.account_is_connected(self.name): 1648 return 1649 if len(contacts) == 1: 1650 msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(), 1651 contacts[0].get_shown_name()) 1652 else: 1653 msg = _('Sent contacts:') 1654 for contact in contacts: 1655 msg += '\n "%s" (%s)' % (contact.get_full_jid(), 1656 contact.get_shown_name()) 1657 msg_iq = common.xmpp.Message(to=jid, body=msg) 1658 x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX) 1659 for contact in contacts: 1660 x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid, 1661 'name': contact.get_shown_name()}) 1662 self.connection.send(msg_iq)
1663
1664 - def send_stanza(self, stanza):
1665 """ 1666 Send a stanza untouched 1667 """ 1668 if not self.connection: 1669 return 1670 self.connection.send(stanza)
1671
1672 - def ack_subscribed(self, jid):
1673 if not gajim.account_is_connected(self.name): 1674 return 1675 log.debug('ack\'ing subscription complete for %s' % jid) 1676 p = common.xmpp.Presence(jid, 'subscribe') 1677 self.connection.send(p)
1678
1679 - def ack_unsubscribed(self, jid):
1680 if not gajim.account_is_connected(self.name): 1681 return 1682 log.debug('ack\'ing unsubscription complete for %s' % jid) 1683 p = common.xmpp.Presence(jid, 'unsubscribe') 1684 self.connection.send(p)
1685
1686 - def request_subscription(self, jid, msg='', name='', groups=[], 1687 auto_auth=False, user_nick=''):
1688 if not gajim.account_is_connected(self.name): 1689 return 1690 log.debug('subscription request for %s' % jid) 1691 if auto_auth: 1692 self.jids_for_auto_auth.append(jid) 1693 # RFC 3921 section 8.2 1694 infos = {'jid': jid} 1695 if name: 1696 infos['name'] = name 1697 iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER) 1698 q = iq.getTag('query') 1699 item = q.addChild('item', attrs=infos) 1700 for g in groups: 1701 item.addChild('group').setData(g) 1702 self.connection.send(iq) 1703 1704 p = common.xmpp.Presence(jid, 'subscribe') 1705 if user_nick: 1706 p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick) 1707 p = self.add_sha(p) 1708 if msg: 1709 p.setStatus(msg) 1710 self.connection.send(p)
1711
1712 - def send_authorization(self, jid):
1713 if not gajim.account_is_connected(self.name): 1714 return 1715 p = common.xmpp.Presence(jid, 'subscribed') 1716 p = self.add_sha(p) 1717 self.connection.send(p)
1718
1719 - def refuse_authorization(self, jid):
1720 if not gajim.account_is_connected(self.name): 1721 return 1722 p = common.xmpp.Presence(jid, 'unsubscribed') 1723 p = self.add_sha(p) 1724 self.connection.send(p)
1725
1726 - def unsubscribe(self, jid, remove_auth = True):
1727 if not gajim.account_is_connected(self.name): 1728 return 1729 if remove_auth: 1730 self.connection.getRoster().delItem(jid) 1731 jid_list = gajim.config.get_per('contacts') 1732 for j in jid_list: 1733 if j.startswith(jid): 1734 gajim.config.del_per('contacts', j) 1735 else: 1736 self.connection.getRoster().Unsubscribe(jid) 1737 self.update_contact(jid, '', [])
1738
1739 - def unsubscribe_agent(self, agent):
1740 if not gajim.account_is_connected(self.name): 1741 return 1742 iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent) 1743 iq.getTag('query').setTag('remove') 1744 id_ = self.connection.getAnID() 1745 iq.setID(id_) 1746 self.awaiting_answers[id_] = (AGENT_REMOVED, agent) 1747 self.connection.send(iq) 1748 self.connection.getRoster().delItem(agent)
1749
1750 - def send_new_account_infos(self, form, is_form):
1751 if is_form: 1752 # Get username and password and put them in new_account_info 1753 for field in form.iter_fields(): 1754 if field.var == 'username': 1755 self.new_account_info['name'] = field.value 1756 if field.var == 'password': 1757 self.new_account_info['password'] = field.value 1758 else: 1759 # Get username and password and put them in new_account_info 1760 if 'username' in form: 1761 self.new_account_info['name'] = form['username'] 1762 if 'password' in form: 1763 self.new_account_info['password'] = form['password'] 1764 self.new_account_form = form 1765 self.new_account(self.name, self.new_account_info)
1766
1767 - def new_account(self, name, config, sync=False):
1768 # If a connection already exist we cannot create a new account 1769 if self.connection: 1770 return 1771 self._hostname = config['hostname'] 1772 self.new_account_info = config 1773 self.name = name 1774 self.on_connect_success = self._on_new_account 1775 self.on_connect_failure = self._on_new_account 1776 self.connect(config)
1777
1778 - def _on_new_account(self, con=None, con_type=None):
1779 if not con_type: 1780 if len(self._connection_types) or len(self._hosts): 1781 # There are still other way to try to connect 1782 return 1783 self.dispatch('NEW_ACC_NOT_CONNECTED', 1784 (_('Could not connect to "%s"') % self._hostname)) 1785 return 1786 self.on_connect_failure = None 1787 self.connection = con 1788 common.xmpp.features_nb.getRegInfo(con, self._hostname)
1789
1790 - def request_os_info(self, jid, resource, groupchat_jid=None):
1791 """ 1792 groupchat_jid is used when we want to send a request to a real jid and 1793 act as if the answer comes from the groupchat_jid 1794 """ 1795 if not gajim.account_is_connected(self.name): 1796 return 1797 # If we are invisible, do not request 1798 if self.connected == gajim.SHOW_LIST.index('invisible'): 1799 self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status'))) 1800 return 1801 to_whom_jid = jid 1802 if resource: 1803 to_whom_jid += '/' + resource 1804 iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\ 1805 common.xmpp.NS_VERSION) 1806 id_ = self.connection.getAnID() 1807 iq.setID(id_) 1808 if groupchat_jid: 1809 self.groupchat_jids[id_] = groupchat_jid 1810 self.version_ids.append(id_) 1811 self.connection.send(iq)
1812
1813 - def request_entity_time(self, jid, resource, groupchat_jid=None):
1814 """ 1815 groupchat_jid is used when we want to send a request to a real jid and 1816 act as if the answer comes from the groupchat_jid 1817 """ 1818 if not gajim.account_is_connected(self.name): 1819 return 1820 # If we are invisible, do not request 1821 if self.connected == gajim.SHOW_LIST.index('invisible'): 1822 self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status'))) 1823 return 1824 to_whom_jid = jid 1825 if resource: 1826 to_whom_jid += '/' + resource 1827 iq = common.xmpp.Iq(to=to_whom_jid, typ='get') 1828 iq.addChild('time', namespace=common.xmpp.NS_TIME_REVISED) 1829 id_ = self.connection.getAnID() 1830 iq.setID(id_) 1831 if groupchat_jid: 1832 self.groupchat_jids[id_] = groupchat_jid 1833 self.entity_time_ids.append(id_) 1834 self.connection.send(iq)
1835
1836 - def get_settings(self):
1837 """ 1838 Get Gajim settings as described in XEP 0049 1839 """ 1840 if not gajim.account_is_connected(self.name): 1841 return 1842 iq = common.xmpp.Iq(typ='get') 1843 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) 1844 iq2.addChild(name='gajim', namespace='gajim:prefs') 1845 self.connection.send(iq)
1846
1847 - def seclabel_catalogue(self, to, callback):
1848 if not gajim.account_is_connected(self.name): 1849 return 1850 self.seclabel_catalogue_request(to, callback) 1851 iq = common.xmpp.Iq(typ='get') 1852 iq2 = iq.addChild(name='catalog', namespace=common.xmpp.NS_SECLABEL_CATALOG) 1853 iq2.setAttr('to', to) 1854 self.connection.send(iq)
1855
1856 - def _request_bookmarks_xml(self):
1857 if not gajim.account_is_connected(self.name): 1858 return 1859 iq = common.xmpp.Iq(typ='get') 1860 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) 1861 iq2.addChild(name='storage', namespace='storage:bookmarks') 1862 self.connection.send(iq)
1863
1864 - def _check_bookmarks_received(self):
1865 if not self.bookmarks: 1866 self._request_bookmarks_xml()
1867
1868 - def get_bookmarks(self, storage_type=None):
1869 """ 1870 Get Bookmarks from storage or PubSub if supported as described in XEP 1871 0048 1872 1873 storage_type can be set to xml to force request to xml storage 1874 """ 1875 if not gajim.account_is_connected(self.name): 1876 return 1877 if self.pubsub_supported and storage_type != 'xml': 1878 self.send_pb_retrieve('', 'storage:bookmarks') 1879 # some server (ejabberd) are so slow to answer that we request via XML 1880 # if we don't get answer in the next 30 seconds 1881 gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30) 1882 else: 1883 self._request_bookmarks_xml()
1884
1885 - def store_bookmarks(self, storage_type=None):
1886 """ 1887 Send bookmarks to the storage namespace or PubSub if supported 1888 1889 storage_type can be set to 'pubsub' or 'xml' so store in only one method 1890 else it will be stored on both 1891 """ 1892 if not gajim.account_is_connected(self.name): 1893 return 1894 iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'}) 1895 for bm in self.bookmarks: 1896 iq2 = iq.addChild(name = "conference") 1897 iq2.setAttr('jid', bm['jid']) 1898 iq2.setAttr('autojoin', bm['autojoin']) 1899 iq2.setAttr('minimize', bm['minimize']) 1900 iq2.setAttr('name', bm['name']) 1901 # Only add optional elements if not empty 1902 # Note: need to handle both None and '' as empty 1903 # thus shouldn't use "is not None" 1904 if bm.get('nick', None): 1905 iq2.setTagData('nick', bm['nick']) 1906 if bm.get('password', None): 1907 iq2.setTagData('password', bm['password']) 1908 if bm.get('print_status', None): 1909 iq2.setTagData('print_status', bm['print_status']) 1910 1911 if self.pubsub_supported and self.pubsub_publish_options_supported and \ 1912 storage_type != 'xml': 1913 options = common.xmpp.Node(common.xmpp.NS_DATA + ' x', 1914 attrs={'type': 'submit'}) 1915 f = options.addChild('field', attrs={'var': 'FORM_TYPE', 1916 'type': 'hidden'}) 1917 f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS) 1918 f = options.addChild('field', attrs={'var': 'pubsub#persist_items'}) 1919 f.setTagData('value', 'true') 1920 f = options.addChild('field', attrs={'var': 'pubsub#access_model'}) 1921 f.setTagData('value', 'whitelist') 1922 self.send_pb_publish('', 'storage:bookmarks', iq, 'current', 1923 options=options) 1924 if storage_type != 'pubsub': 1925 iqA = common.xmpp.Iq(typ='set') 1926 iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) 1927 iqB.addChild(node=iq) 1928 self.connection.send(iqA)
1929
1930 - def get_annotations(self):
1931 """ 1932 Get Annonations from storage as described in XEP 0048, and XEP 0145 1933 """ 1934 self.annotations = {} 1935 if not gajim.account_is_connected(self.name): 1936 return 1937 iq = common.xmpp.Iq(typ='get') 1938 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) 1939 iq2.addChild(name='storage', namespace='storage:rosternotes') 1940 self.connection.send(iq)
1941
1942 - def store_annotations(self):
1943 """ 1944 Set Annonations in private storage as described in XEP 0048, and XEP 0145 1945 """ 1946 if not gajim.account_is_connected(self.name): 1947 return 1948 iq = common.xmpp.Iq(typ='set') 1949 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) 1950 iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes') 1951 for jid in self.annotations.keys(): 1952 if self.annotations[jid]: 1953 iq4 = iq3.addChild(name = "note") 1954 iq4.setAttr('jid', jid) 1955 iq4.setData(self.annotations[jid]) 1956 self.connection.send(iq)
1957 1958
1959 - def get_metacontacts(self):
1960 """ 1961 Get metacontacts list from storage as described in XEP 0049 1962 """ 1963 if not gajim.account_is_connected(self.name): 1964 return 1965 iq = common.xmpp.Iq(typ='get') 1966 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) 1967 iq2.addChild(name='storage', namespace='storage:metacontacts') 1968 id_ = self.connection.getAnID() 1969 iq.setID(id_) 1970 self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, ) 1971 self.connection.send(iq)
1972
1973 - def store_metacontacts(self, tags_list):
1974 """ 1975 Send meta contacts to the storage namespace 1976 """ 1977 if not gajim.account_is_connected(self.name): 1978 return 1979 iq = common.xmpp.Iq(typ='set') 1980 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE) 1981 iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts') 1982 for tag in tags_list: 1983 for data in tags_list[tag]: 1984 jid = data['jid'] 1985 dict_ = {'jid': jid, 'tag': tag} 1986 if 'order' in data: 1987 dict_['order'] = data['order'] 1988 iq3.addChild(name = 'meta', attrs = dict_) 1989 self.connection.send(iq)
1990
1991 - def send_agent_status(self, agent, ptype):
1992 if not gajim.account_is_connected(self.name): 1993 return 1994 show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) 1995 p = common.xmpp.Presence(to = agent, typ = ptype, show = show) 1996 p = self.add_sha(p, ptype != 'unavailable') 1997 self.connection.send(p)
1998
1999 - def send_captcha(self, jid, form_node):
2000 if not gajim.account_is_connected(self.name): 2001 return 2002 iq = common.xmpp.Iq(typ='set', to=jid) 2003 captcha = iq.addChild(name='captcha', namespace=common.xmpp.NS_CAPTCHA) 2004 captcha.addChild(node=form_node) 2005 self.connection.send(iq)
2006
2007 - def check_unique_room_id_support(self, server, instance):
2008 if not gajim.account_is_connected(self.name): 2009 return 2010 iq = common.xmpp.Iq(typ = 'get', to = server) 2011 iq.setAttr('id', 'unique1') 2012 iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE) 2013 def _on_response(resp): 2014 if not common.xmpp.isResultNode(resp): 2015 self.dispatch('UNIQUE_ROOM_ID_UNSUPPORTED', (server, instance)) 2016 return 2017 self.dispatch('UNIQUE_ROOM_ID_SUPPORTED', (server, instance, 2018 resp.getTag('unique').getData()))
2019 self.connection.SendAndCallForResponse(iq, _on_response) 2020
2021 - def join_gc(self, nick, room_jid, password, change_nick=False):
2022 # FIXME: This room JID needs to be normalized; see #1364 2023 if not gajim.account_is_connected(self.name): 2024 return 2025 show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected]) 2026 if show == 'invisible': 2027 # Never join a room when invisible 2028 return 2029 2030 # last date/time in history to avoid duplicate 2031 if room_jid not in self.last_history_time: 2032 # Not in memory, get it from DB 2033 last_log = None 2034 # Do not check if we are not logging for this room 2035 if gajim.config.should_log(self.name, room_jid): 2036 # Check time first in the FAST table 2037 last_log = gajim.logger.get_room_last_message_time(room_jid) 2038 if last_log is None: 2039 # Not in special table, get it from messages DB 2040 last_log = gajim.logger.get_last_date_that_has_logs(room_jid, 2041 is_room=True) 2042 # Create self.last_history_time[room_jid] even if not logging, 2043 # could be used in connection_handlers 2044 if last_log is None: 2045 last_log = 0 2046 self.last_history_time[room_jid] = last_log 2047 2048 p = common.xmpp.Presence(to='%s/%s' % (room_jid, nick), 2049 show=show, status=self.status) 2050 h = hmac.new(self.secret_hmac, room_jid).hexdigest()[:6] 2051 id_ = self.connection.getAnID() 2052 id_ = 'gajim_muc_' + id_ + '_' + h 2053 p.setID(id_) 2054 if gajim.config.get('send_sha_in_gc_presence'): 2055 p = self.add_sha(p) 2056 self.add_lang(p) 2057 if not change_nick: 2058 t = p.setTag(common.xmpp.NS_MUC + ' x') 2059 last_date = self.last_history_time[room_jid] 2060 if last_date == 0: 2061 last_date = time.time() - gajim.config.get( 2062 'muc_restore_timeout') * 60 2063 else: 2064 last_date = min(last_date, time.time() - gajim.config.get( 2065 'muc_restore_timeout') * 60) 2066 last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(last_date)) 2067 t.setTag('history', {'maxstanzas': gajim.config.get( 2068 'muc_restore_lines'), 'since': last_date}) 2069 if password: 2070 t.setTagData('password', password) 2071 self.connection.send(p)
2072
2073 - def send_gc_message(self, jid, msg, xhtml = None, label = None):
2074 if not gajim.account_is_connected(self.name): 2075 return 2076 if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): 2077 from common.rst_xhtml_generator import create_xhtml 2078 xhtml = create_xhtml(msg) 2079 msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml) 2080 if label is not None: 2081 msg_iq.addChild(node = label) 2082 self.connection.send(msg_iq) 2083 self.dispatch('MSGSENT', (jid, msg))
2084
2085 - def send_gc_subject(self, jid, subject):
2086 if not gajim.account_is_connected(self.name): 2087 return 2088 msg_iq = common.xmpp.Message(jid, typ = 'groupchat', subject = subject) 2089 self.connection.send(msg_iq)
2090
2091 - def request_gc_config(self, room_jid):
2092 if not gajim.account_is_connected(self.name): 2093 return 2094 iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER, 2095 to = room_jid) 2096 self.add_lang(iq) 2097 self.connection.send(iq)
2098
2099 - def destroy_gc_room(self, room_jid, reason = '', jid = ''):
2100 if not gajim.account_is_connected(self.name): 2101 return 2102 iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER, 2103 to = room_jid) 2104 destroy = iq.getTag('query').setTag('destroy') 2105 if reason: 2106 destroy.setTagData('reason', reason) 2107 if jid: 2108 destroy.setAttr('jid', jid) 2109 self.connection.send(iq)
2110
2111 - def send_gc_status(self, nick, jid, show, status):
2112 if not gajim.account_is_connected(self.name): 2113 return 2114 if show == 'invisible': 2115 show = 'offline' 2116 ptype = None 2117 if show == 'offline': 2118 ptype = 'unavailable' 2119 xmpp_show = helpers.get_xmpp_show(show) 2120 p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype, 2121 show = xmpp_show, status = status) 2122 h = hmac.new(self.secret_hmac, jid).hexdigest()[:6] 2123 id_ = self.connection.getAnID() 2124 id_ = 'gajim_muc_' + id_ + '_' + h 2125 p.setID(id_) 2126 if gajim.config.get('send_sha_in_gc_presence') and show != 'offline': 2127 p = self.add_sha(p, ptype != 'unavailable') 2128 self.add_lang(p) 2129 # send instantly so when we go offline, status is sent to gc before we 2130 # disconnect from jabber server 2131 self.connection.send(p)
2132
2133 - def gc_got_disconnected(self, room_jid):
2134 """ 2135 A groupchat got disconnected. This can be or purpose or not 2136 2137 Save the time we had last message to avoid duplicate logs AND be faster 2138 than get that date from DB. Save time that we have in mem in a small 2139 table (with fast access) 2140 """ 2141 gajim.logger.set_room_last_message_time(room_jid, self.last_history_time[room_jid])
2142
2143 - def gc_set_role(self, room_jid, nick, role, reason = ''):
2144 """ 2145 Role is for all the life of the room so it's based on nick 2146 """ 2147 if not gajim.account_is_connected(self.name): 2148 return 2149 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ 2150 common.xmpp.NS_MUC_ADMIN) 2151 item = iq.getTag('query').setTag('item') 2152 item.setAttr('nick', nick) 2153 item.setAttr('role', role) 2154 if reason: 2155 item.addChild(name = 'reason', payload = reason) 2156 self.connection.send(iq)
2157
2158 - def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
2159 """ 2160 Affiliation is for all the life of the room so it's based on jid 2161 """ 2162 if not gajim.account_is_connected(self.name): 2163 return 2164 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ 2165 common.xmpp.NS_MUC_ADMIN) 2166 item = iq.getTag('query').setTag('item') 2167 item.setAttr('jid', jid) 2168 item.setAttr('affiliation', affiliation) 2169 if reason: 2170 item.addChild(name = 'reason', payload = reason) 2171 self.connection.send(iq)
2172
2173 - def send_gc_affiliation_list(self, room_jid, users_dict):
2174 if not gajim.account_is_connected(self.name): 2175 return 2176 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \ 2177 common.xmpp.NS_MUC_ADMIN) 2178 item = iq.getTag('query') 2179 for jid in users_dict: 2180 item_tag = item.addChild('item', {'jid': jid, 2181 'affiliation': users_dict[jid]['affiliation']}) 2182 if 'reason' in users_dict[jid] and users_dict[jid]['reason']: 2183 item_tag.setTagData('reason', users_dict[jid]['reason']) 2184 self.connection.send(iq)
2185
2186 - def get_affiliation_list(self, room_jid, affiliation):
2187 if not gajim.account_is_connected(self.name): 2188 return 2189 iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \ 2190 common.xmpp.NS_MUC_ADMIN) 2191 item = iq.getTag('query').setTag('item') 2192 item.setAttr('affiliation', affiliation) 2193 self.connection.send(iq)
2194
2195 - def send_gc_config(self, room_jid, form):
2196 if not gajim.account_is_connected(self.name): 2197 return 2198 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\ 2199 common.xmpp.NS_MUC_OWNER) 2200 query = iq.getTag('query') 2201 form.setAttr('type', 'submit') 2202 query.addChild(node = form) 2203 self.connection.send(iq)
2204
2205 - def change_password(self, password):
2206 if not gajim.account_is_connected(self.name): 2207 return 2208 hostname = gajim.config.get_per('accounts', self.name, 'hostname') 2209 username = gajim.config.get_per('accounts', self.name, 'name') 2210 iq = common.xmpp.Iq(typ = 'set', to = hostname) 2211 q = iq.setTag(common.xmpp.NS_REGISTER + ' query') 2212 q.setTagData('username', username) 2213 q.setTagData('password', password) 2214 self.connection.send(iq)
2215
2216 - def get_password(self, callback, type_):
2217 self.pasword_callback = (callback, type_) 2218 if self.password: 2219 self.set_password(self.password) 2220 return 2221 self.dispatch('PASSWORD_REQUIRED', None)
2222
2223 - def set_password(self, password):
2224 self.password = password 2225 if self.pasword_callback: 2226 callback, type_ = self.pasword_callback 2227 if self._current_type == 'plain' and type_ == 'PLAIN' and \ 2228 gajim.config.get_per('accounts', self.name, 2229 'warn_when_insecure_password'): 2230 self.dispatch('INSECURE_PASSWORD', None) 2231 return 2232 callback(password) 2233 self.pasword_callback = None
2234
2235 - def accept_insecure_password(self):
2236 if self.pasword_callback: 2237 callback, type_ = self.pasword_callback 2238 callback(self.password) 2239 self.pasword_callback = None
2240
2241 - def unregister_account(self, on_remove_success):
2242 # no need to write this as a class method and keep the value of 2243 # on_remove_success as a class property as pass it as an argument 2244 def _on_unregister_account_connect(con): 2245 self.on_connect_auth = None 2246 if gajim.account_is_connected(self.name): 2247 hostname = gajim.config.get_per('accounts', self.name, 'hostname') 2248 iq = common.xmpp.Iq(typ = 'set', to = hostname) 2249 iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove') 2250 def _on_answer(result): 2251 if result.getType() == 'result': 2252 on_remove_success(True) 2253 return 2254 self.dispatch('ERROR', (_('Unregister failed'), 2255 _('Unregistration with server %(server)s failed: %(error)s') \ 2256 % {'server': hostname, 'error': result.getErrorMsg()})) 2257 on_remove_success(False)
2258 con.SendAndCallForResponse(iq, _on_answer) 2259 return 2260 on_remove_success(False) 2261 if self.connected == 0: 2262 self.on_connect_auth = _on_unregister_account_connect 2263 self.connect_and_auth() 2264 else: 2265 _on_unregister_account_connect(self.connection) 2266
2267 - def send_invite(self, room, to, reason='', continue_tag=False):
2268 """ 2269 Send invitation 2270 """ 2271 message=common.xmpp.Message(to = room) 2272 c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER) 2273 c = c.addChild(name = 'invite', attrs={'to' : to}) 2274 if continue_tag: 2275 c.addChild(name = 'continue') 2276 if reason != '': 2277 c.setTagData('reason', reason) 2278 self.connection.send(message)
2279
2280 - def check_pingalive(self):
2281 if self.awaiting_xmpp_ping_id: 2282 # We haven't got the pong in time, disco and reconnect 2283 log.warn("No reply received for keepalive ping. Reconnecting.") 2284 self._disconnectedReconnCB()
2285
2286 - def _reconnect_alarm(self):
2287 if self.time_to_reconnect: 2288 if self.connected < 2: 2289 self._reconnect() 2290 else: 2291 self.time_to_reconnect = None
2292
2293 - def request_search_fields(self, jid):
2294 iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \ 2295 common.xmpp.NS_SEARCH) 2296 self.connection.send(iq)
2297
2298 - def send_search_form(self, jid, form, is_form):
2299 iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \ 2300 common.xmpp.NS_SEARCH) 2301 item = iq.getTag('query') 2302 if is_form: 2303 item.addChild(node = form) 2304 else: 2305 for i in form.keys(): 2306 item.setTagData(i, form[i]) 2307 def _on_response(resp): 2308 jid = jid = helpers.get_jid_from_iq(resp) 2309 tag = resp.getTag('query', namespace = common.xmpp.NS_SEARCH) 2310 if not tag: 2311 self.dispatch('SEARCH_RESULT', (jid, None, False)) 2312 return 2313 df = tag.getTag('x', namespace = common.xmpp.NS_DATA) 2314 if df: 2315 self.dispatch('SEARCH_RESULT', (jid, df, True)) 2316 return 2317 df = [] 2318 for item in tag.getTags('item'): 2319 # We also show attributes. jid is there 2320 f = item.attrs 2321 for i in item.getPayload(): 2322 f[i.getName()] = i.getData() 2323 df.append(f) 2324 self.dispatch('SEARCH_RESULT', (jid, df, False))
2325 2326 self.connection.SendAndCallForResponse(iq, _on_response) 2327
2328 - def load_roster_from_db(self):
2329 roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name)) 2330 self.dispatch('ROSTER', roster)
2331 2332 2333 # END Connection 2334