connection.py 121 KB
Newer Older
roidelapluie's avatar
roidelapluie committed
1
# -*- coding:utf-8 -*-
roidelapluie's avatar
roidelapluie committed
2
## src/common/connection.py
3
##
roidelapluie's avatar
roidelapluie committed
4
## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
Yann Leboulanger's avatar
Yann Leboulanger committed
5
## Copyright (C) 2003-2013 Yann Leboulanger <asterix AT lagaule.org>
roidelapluie's avatar
roidelapluie committed
6
## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
roidelapluie's avatar
roidelapluie committed
7
##                    Stéphan Kochen <stephan AT kochen.nl>
roidelapluie's avatar
roidelapluie committed
8 9 10
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
##                         Travis Shirk <travis AT pobox.com>
##                         Nikos Kouremenos <kourem AT gmail.com>
roidelapluie's avatar
roidelapluie committed
11
## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
roidelapluie's avatar
roidelapluie committed
12 13 14 15 16 17 18
##                    Stefan Bethge <stefan AT lanpartei.de>
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
##                    Julien Pivotto <roidelapluie AT gmail.com>
## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
##                    Jonathan Schleifer <js-gajim AT webkeks.org>
19
##
Yann Leboulanger's avatar
Yann Leboulanger committed
20 21 22
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
23
## it under the terms of the GNU General Public License as published
Yann Leboulanger's avatar
Yann Leboulanger committed
24
## by the Free Software Foundation; version 3 only.
25
##
Yann Leboulanger's avatar
Yann Leboulanger committed
26
## Gajim is distributed in the hope that it will be useful,
27
## but WITHOUT ANY WARRANTY; without even the implied warranty of
roidelapluie's avatar
roidelapluie committed
28
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 30
## GNU General Public License for more details.
##
Yann Leboulanger's avatar
Yann Leboulanger committed
31
## You should have received a copy of the GNU General Public License
roidelapluie's avatar
roidelapluie committed
32
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
Yann Leboulanger's avatar
Yann Leboulanger committed
33
##
34 35

import os
36
import random
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
37
import socket
38
import operator
39

Yann Leboulanger's avatar
Yann Leboulanger committed
40
import time
41
import locale
42
import hmac
43
import json
Yann Leboulanger's avatar
Yann Leboulanger committed
44

45
try:
46
    randomsource = random.SystemRandom()
47
except Exception:
48 49
    randomsource = random.Random()
    randomsource.seed()
50

51
import signal
Yann Leboulanger's avatar
Yann Leboulanger committed
52
if os.name != 'nt':
53
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
dkirov's avatar
dkirov committed
54

55
import nbxmpp
Yann Leboulanger's avatar
Yann Leboulanger committed
56
from common import helpers
Yann Leboulanger's avatar
Yann Leboulanger committed
57
from common import gajim
58
from common import gpg
59
from common import passwords
60
from common import exceptions
61
from common import check_X509
62
from connection_handlers import *
63

64
from nbxmpp import Smacks
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
65
from string import Template
66
import logging
67
log = logging.getLogger('gajim.c.connection')
68

Yann Leboulanger's avatar
Yann Leboulanger committed
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
ssl_error = {
2: _("Unable to get issuer certificate"),
3: _("Unable to get certificate CRL"),
4: _("Unable to decrypt certificate's signature"),
5: _("Unable to decrypt CRL's signature"),
6: _("Unable to decode issuer public key"),
7: _("Certificate signature failure"),
8: _("CRL signature failure"),
9: _("Certificate is not yet valid"),
10: _("Certificate has expired"),
11: _("CRL is not yet valid"),
12: _("CRL has expired"),
13: _("Format error in certificate's notBefore field"),
14: _("Format error in certificate's notAfter field"),
15: _("Format error in CRL's lastUpdate field"),
16: _("Format error in CRL's nextUpdate field"),
17: _("Out of memory"),
steve-e's avatar
steve-e committed
86 87 88
18: _("Self signed certificate"),
19: _("Self signed certificate in certificate chain"),
20: _("Unable to get local issuer certificate"),
Yann Leboulanger's avatar
Yann Leboulanger committed
89 90 91 92 93 94 95 96 97 98 99 100 101
21: _("Unable to verify the first certificate"),
22: _("Certificate chain too long"),
23: _("Certificate revoked"),
24: _("Invalid CA certificate"),
25: _("Path length constraint exceeded"),
26: _("Unsupported certificate purpose"),
27: _("Certificate not trusted"),
28: _("Certificate rejected"),
29: _("Subject issuer mismatch"),
30: _("Authority and subject key identifier mismatch"),
31: _("Authority and issuer serial number mismatch"),
32: _("Key usage does not include certificate signing"),
50: _("Application verification failure")
102
#100 is for internal usage: host not correct
103
}
104 105

class CommonConnection:
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
    """
    Common connection class, can be derivated for normal connection or zeroconf
    connection
    """

    def __init__(self, name):
        self.name = name
        # self.connected:
        # 0=>offline,
        # 1=>connection in progress,
        # 2=>online
        # 3=>free for chat
        # ...
        self.connected = 0
        self.connection = None # xmpppy ClientCommon instance
        self.on_purpose = False
        self.is_zeroconf = False
        self.password = ''
        self.server_resource = self._compute_resource()
        self.gpg = None
        self.USE_GPG = False
        if gajim.HAVE_GPG:
            self.USE_GPG = True
129
            self.gpg = gpg.GnuPG(gajim.config.get('use_gpg_agent'))
130 131 132 133 134 135 136 137 138 139 140
        self.status = ''
        self.old_show = ''
        self.priority = gajim.get_priority(name, 'offline')
        self.time_to_reconnect = None
        self.bookmarks = []

        self.blocked_list = []
        self.blocked_contacts = []
        self.blocked_groups = []
        self.blocked_all = False

141 142 143
        self.seclabel_supported = False
        self.seclabel_catalogues = {}

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
        self.pep_supported = False
        self.pep = {}
        # Do we continue connection when we get roster (send presence,get vcard..)
        self.continue_connect_info = None

        # Remember where we are in the register agent process
        self.agent_registrations = {}
        # To know the groupchat jid associated with a sranza ID. Useful to
        # request vcard or os info... to a real JID but act as if it comes from
        # the fake jid
        self.groupchat_jids = {} # {ID : groupchat_jid}

        self.privacy_rules_supported = False
        self.vcard_supported = False
        self.private_storage_supported = False
159
        self.archiving_supported = False
Yann Leboulanger's avatar
Yann Leboulanger committed
160
        self.archive_pref_supported = False
161
        self.roster_supported = True
162
        self.blocking_supported = False
163 164 165 166

        self.muc_jid = {} # jid of muc server for each transport type
        self._stun_servers = [] # STUN servers of our jabber server

167 168
        self.awaiting_cids = {} # Used for XEP-0231

169 170
        self.nested_group_delimiter = '::'

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
        self.get_config_values_or_default()

    def _compute_resource(self):
        resource = gajim.config.get_per('accounts', self.name, 'resource')
        # All valid resource substitution strings should be added to this hash.
        if resource:
            resource = Template(resource).safe_substitute({
                    'hostname': socket.gethostname()
            })
        return resource

    def dispatch(self, event, data):
        """
        Always passes account name as first param
        """
        gajim.ged.raise_event(event, self.name, data)

    def _reconnect(self):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def quit(self, kill_core):
        if kill_core and gajim.account_is_connected(self.name):
            self.disconnect(on_purpose=True)

    def test_gpg_passphrase(self, password):
        """
        Returns 'ok', 'bad_pass' or 'expired'
        """
        if not self.gpg:
            return False
        self.gpg.passphrase = password
        keyID = gajim.config.get_per('accounts', self.name, 'keyid')
        signed = self.gpg.sign('test', keyID)
        self.gpg.password = None
        if signed == 'KEYEXPIRED':
            return 'expired'
        elif signed == 'BAD_PASSPHRASE':
            return 'bad_pass'
        return 'ok'

    def get_signed_msg(self, msg, callback = None):
        """
        Returns the signed message if possible or an empty string if gpg is not
        used or None if waiting for passphrase

        callback is the function to call when user give the passphrase
        """
        signed = ''
        keyID = gajim.config.get_per('accounts', self.name, 'keyid')
        if keyID and self.USE_GPG:
            use_gpg_agent = gajim.config.get('use_gpg_agent')
            if self.gpg.passphrase is None and not use_gpg_agent:
                # We didn't set a passphrase
                return None
228 229 230 231 232 233
            signed = self.gpg.sign(msg, keyID)
            if signed == 'BAD_PASSPHRASE':
                self.USE_GPG = False
                signed = ''
                gajim.nec.push_incoming_event(BadGPGPassphraseEvent(None,
                    conn=self))
234 235 236 237 238 239 240
        return signed

    def _on_disconnected(self):
        """
        Called when a disconnect request has completed successfully
        """
        self.disconnect(on_purpose=True)
241 242
        gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
            show='offline'))
243 244 245 246 247 248 249 250 251 252 253 254

    def get_status(self):
        return gajim.SHOW_LIST[self.connected]

    def check_jid(self, jid):
        """
        This function must be implemented by derivated classes. It has to return
        the valid jid, or raise a helpers.InvalidFormat exception
        """
        raise NotImplementedError

    def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
255 256
    chatstate=None, msg_id=None, resource=None, user_nick=None, xhtml=None,
    session=None, forward_from=None, form_node=None, label=None,
257 258
    original_message=None, delayed=None, attention=False, correction_msg=None,
    callback=None):
259 260
        if not self.connection or self.connected < 2:
            return 1
261

262 263 264
        try:
            jid = self.check_jid(jid)
        except helpers.InvalidFormat:
265 266 267 268
            gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
                level='error', pri_txt=_('Invalid Jabber ID'), sec_txt=_(
                'It is not possible to send a message to %s, this JID is not '
                'valid.') % jid))
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
            return

        if msg and not xhtml and gajim.config.get(
        'rst_formatting_outgoing_messages'):
            from common.rst_xhtml_generator import create_xhtml
            xhtml = create_xhtml(msg)
        if not msg and chatstate is None and form_node is None:
            return
        fjid = jid
        if resource:
            fjid += '/' + resource
        msgtxt = msg
        msgenc = ''

        if session:
            fjid = session.get_to()

        if keyID and self.USE_GPG:
            xhtml = None
            if keyID ==  'UNKNOWN':
                error = _('Neither the remote presence is signed, nor a key was '
                        'assigned.')
            elif keyID.endswith('MISMATCH'):
                error = _('The contact\'s key (%s) does not match the key assigned '
                        'in Gajim.' % keyID[:8])
            else:
                def encrypt_thread(msg, keyID, always_trust=False):
                    # encrypt message. This function returns (msgenc, error)
297 298
                    return self.gpg.encrypt(msg.encode('utf-8'), [keyID],
                        always_trust)
299 300 301 302 303 304 305 306
                def _on_encrypted(output):
                    msgenc, error = output
                    if error == 'NOT_TRUSTED':
                        def _on_always_trust(answer):
                            if answer:
                                gajim.thread_interface(encrypt_thread, [msg, keyID,
                                        True], _on_encrypted, [])
                            else:
307 308 309
                                self._message_encrypted_cb(output, type_, msg,
                                    msgtxt, original_message, fjid, resource,
                                    jid, xhtml, subject, chatstate, msg_id,
310
                                    label, forward_from, delayed, session,
311
                                    form_node, user_nick, keyID, attention,
312
                                    correction_msg, callback)
Yann Leboulanger's avatar
Yann Leboulanger committed
313
                        gajim.nec.push_incoming_event(GPGTrustKeyEvent(None,
314
                            conn=self, callback=_on_always_trust))
315 316
                    else:
                        self._message_encrypted_cb(output, type_, msg, msgtxt,
317
                            original_message, fjid, resource, jid, xhtml,
318 319
                            subject, chatstate, msg_id, label, forward_from,
                            delayed, session, form_node, user_nick, keyID,
320
                            attention, correction_msg, callback)
321
                gajim.thread_interface(encrypt_thread, [msg, keyID, False],
322
                    _on_encrypted, [])
323 324 325
                return

            self._message_encrypted_cb(('', error), type_, msg, msgtxt,
326
                original_message, fjid, resource, jid, xhtml, subject,
327
                chatstate, msg_id, label, forward_from, delayed, session,
328 329
                form_node, user_nick, keyID, attention, correction_msg,
                callback)
330
            return
331 332

        self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
333
            resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
334
            label, forward_from, delayed, session, form_node, user_nick,
335
            attention, correction_msg, callback)
336 337 338

    def _message_encrypted_cb(self, output, type_, msg, msgtxt,
    original_message, fjid, resource, jid, xhtml, subject, chatstate, msg_id,
339
    label, forward_from, delayed, session, form_node, user_nick, keyID,
340
    attention, correction_msg, callback):
341 342 343 344 345
        msgenc, error = output

        if msgenc and not error:
            msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
            lang = os.getenv('LANG')
346 347
            if lang is not None and not lang.startswith('en'):
                # we're not english: one in locale and one en
348 349
                msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
                        ' (' + msgtxt + ')'
350 351
            self._on_continue_message(type_, msg, msgtxt, original_message,
                fjid, resource, jid, xhtml, subject, msgenc, keyID,
352
                chatstate, msg_id, label, forward_from, delayed, session,
353
                form_node, user_nick, attention, correction_msg, callback)
354 355 356
            return
        # Encryption failed, do not send message
        tim = localtime()
357 358
        gajim.nec.push_incoming_event(MessageNotSentEvent(None, conn=self,
            jid=jid, message=msgtxt, error=error, time_=tim, session=session))
359 360

    def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
361
    resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
362
    label, forward_from, delayed, session, form_node, user_nick, attention,
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
    correction_msg, callback):

        if correction_msg:
            id_ = correction_msg.getID()
            if correction_msg.getTag('replace'):
                correction_msg.delChild('replace')
            correction_msg.setTag('replace', attrs={'id': id_},
                namespace=nbxmpp.NS_CORRECT)
            id2 = self.connection.getAnID()
            correction_msg.setID(id2)
            correction_msg.setBody(msgtxt)
            if xhtml:
                correction_msg.setXHTML(xhtml)

            if session:
                session.last_send = time.time()

                # XEP-0200
                if session.enable_encryption:
                    correction_msg = session.encrypt_stanza(correction_msg)

            if callback:
                callback(jid, msg, keyID, forward_from, session, original_message,
                    subject, type_, correction_msg, xhtml)
            return

389
        if type_ == 'chat':
390
            msg_iq = nbxmpp.Message(to=fjid, body=msgtxt, typ=type_,
391 392 393
                    xhtml=xhtml)
        else:
            if subject:
394
                msg_iq = nbxmpp.Message(to=fjid, body=msgtxt, typ='normal',
395 396
                        subject=subject, xhtml=xhtml)
            else:
397
                msg_iq = nbxmpp.Message(to=fjid, body=msgtxt, typ='normal',
398
                        xhtml=xhtml)
399 400 401 402

        if msg_id:
            msg_iq.setID(msg_id)

403
        if msgenc:
404
            msg_iq.setTag(nbxmpp.NS_ENCRYPTED + ' x').setData(msgenc)
405 406 407

        if form_node:
            msg_iq.addChild(node=form_node)
408 409
        if label:
            msg_iq.addChild(node=label)
410 411 412

        # XEP-0172: user_nickname
        if user_nick:
413
            msg_iq.setTag('nick', namespace = nbxmpp.NS_NICK).setData(
414 415 416 417 418 419 420 421 422 423
                    user_nick)

        # TODO: We might want to write a function so we don't need to
        #       reproduce that ugly if somewhere else.
        if resource:
            contact = gajim.contacts.get_contact(self.name, jid, resource)
        else:
            contact = gajim.contacts.get_contact_with_highest_priority(self.name,
                    jid)

424 425 426 427 428
        # chatstates - if peer supports xep85, send chatstates
        # please note that the only valid tag inside a message containing a
        # <body> tag is the active event
        if chatstate and contact and contact.supports(NS_CHATSTATES):
            msg_iq.setTag(chatstate, namespace=NS_CHATSTATES)
429 430 431

        if forward_from:
            addresses = msg_iq.addChild('addresses',
432
                namespace=nbxmpp.NS_ADDRESS)
433
            addresses.addChild('address', attrs = {'type': 'ofrom',
434
                'jid': forward_from})
435 436 437 438 439 440

        # XEP-0203
        if delayed:
            our_jid = gajim.get_jid_from_account(self.name) + '/' + \
                    self.server_resource
            timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed))
441
            msg_iq.addChild('delay', namespace=nbxmpp.NS_DELAY2,
442 443 444 445 446
                    attrs={'from': our_jid, 'stamp': timestamp})

        # XEP-0184
        if msgtxt and gajim.config.get_per('accounts', self.name,
        'request_receipt') and contact and contact.supports(
447 448
        nbxmpp.NS_RECEIPTS):
            msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS)
449 450 451 452 453 454 455 456 457 458

        if session:
            # XEP-0201
            session.last_send = time.time()
            msg_iq.setThread(session.thread_id)

            # XEP-0200
            if session.enable_encryption:
                msg_iq = session.encrypt_stanza(msg_iq)

459 460
        # XEP-0224
        if attention:
461
            msg_iq.setTag('attention', namespace=nbxmpp.NS_ATTENTION)
462

463 464
        if callback:
            callback(jid, msg, keyID, forward_from, session, original_message,
465
                subject, type_, msg_iq, xhtml)
466 467

    def log_message(self, jid, msg, forward_from, session, original_message,
468
    subject, type_, xhtml=None):
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
        if not forward_from and session and session.is_loggable():
            ji = gajim.get_jid_without_resource(jid)
            if gajim.config.should_log(self.name, ji):
                log_msg = msg
                if original_message is not None:
                    log_msg = original_message
                if subject:
                    log_msg = _('Subject: %(subject)s\n%(message)s') % \
                    {'subject': subject, 'message': log_msg}
                if log_msg:
                    if type_ == 'chat':
                        kind = 'chat_msg_sent'
                    else:
                        kind = 'single_msg_sent'
                    try:
484 485
                        if xhtml and gajim.config.get('log_xhtml_messages'):
                            log_msg = '<body xmlns="%s">%s</body>' % (
486
                                nbxmpp.NS_XHTML, xhtml)
487 488
                        gajim.logger.write(kind, jid, log_msg)
                    except exceptions.PysqliteOperationalError, e:
489 490
                        self.dispatch('DB_ERROR', (_('Disk Write Error'),
                            str(e)))
491 492
                    except exceptions.DatabaseMalformed:
                        pritext = _('Database Error')
Yann Leboulanger's avatar
Yann Leboulanger committed
493 494 495 496 497
                        sectext = _('The database file (%s) cannot be read. Try'
                            ' to repair it (see '
                            'http://trac.gajim.org/wiki/DatabaseBackup)'
                            ' or remove it (all history will be lost).') % \
                            common.logger.LOG_DB_PATH
498
                        self.dispatch('DB_ERROR', (pritext, sectext))
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543

    def ack_subscribed(self, jid):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def ack_unsubscribed(self, jid):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def request_subscription(self, jid, msg='', name='', groups=[],
                    auto_auth=False):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def send_authorization(self, jid):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def refuse_authorization(self, jid):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def unsubscribe(self, jid, remove_auth = True):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def unsubscribe_agent(self, agent):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def update_contact(self, jid, name, groups):
544
        if self.connection and self.roster_supported:
545 546 547 548 549 550
            self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)

    def update_contacts(self, contacts):
        """
        Update multiple roster items
        """
551
        if self.connection and self.roster_supported:
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
            self.connection.getRoster().setItemMulti(contacts)

    def new_account(self, name, config, sync=False):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def _on_new_account(self, con=None, con_type=None):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def account_changed(self, new_name):
        self.name = new_name

569
    def request_last_status_time(self, jid, resource, groupchat_jid=None):
570
        """
571 572
        groupchat_jid is used when we want to send a request to a real jid and
        act as if the answer comes from the groupchat_jid
573
        """
574
        if not gajim.account_is_connected(self.name):
575 576 577 578
            return
        to_whom_jid = jid
        if resource:
            to_whom_jid += '/' + resource
579
        iq = nbxmpp.Iq(to=to_whom_jid, typ='get', queryNS=nbxmpp.NS_LAST)
580 581 582 583 584 585
        id_ = self.connection.getAnID()
        iq.setID(id_)
        if groupchat_jid:
            self.groupchat_jids[id_] = groupchat_jid
        self.last_ids.append(id_)
        self.connection.send(iq)
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632

    def request_os_info(self, jid, resource):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def get_settings(self):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def get_bookmarks(self):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def store_bookmarks(self):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def get_metacontacts(self):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def send_agent_status(self, agent, ptype):
        """
        To be implemented by derivated classes
        """
        raise NotImplementedError

    def gpg_passphrase(self, passphrase):
        if self.gpg:
            use_gpg_agent = gajim.config.get('use_gpg_agent')
            if use_gpg_agent:
                self.gpg.passphrase = None
            else:
                self.gpg.passphrase = passphrase

    def ask_gpg_keys(self):
        if self.gpg:
633
            return self.gpg.get_keys()
634 635 636 637
        return None

    def ask_gpg_secrete_keys(self):
        if self.gpg:
638
            return self.gpg.get_secret_keys()
639 640 641 642 643 644 645 646
        return None

    def load_roster_from_db(self):
        # Do nothing by default
        return

    def _event_dispatcher(self, realm, event, data):
        if realm == '':
647
            if event == nbxmpp.transports_nb.DATA_RECEIVED:
648 649
                gajim.nec.push_incoming_event(StanzaReceivedEvent(None,
                    conn=self, stanza_str=unicode(data, errors='ignore')))
650
            elif event == nbxmpp.transports_nb.DATA_SENT:
651 652
                gajim.nec.push_incoming_event(StanzaSentEvent(None, conn=self,
                    stanza_str=unicode(data)))
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670

    def change_status(self, show, msg, auto=False):
        if not msg:
            msg = ''
        sign_msg = False
        if not auto and not show == 'offline':
            sign_msg = True
        if show != 'invisible':
            # We save it only when privacy list is accepted
            self.status = msg
        if show != 'offline' and self.connected < 1:
            # set old_show to requested 'show' in case we need to
            # recconect before we auth to server
            self.old_show = show
            self.on_purpose = False
            self.server_resource = self._compute_resource()
            if gajim.HAVE_GPG:
                self.USE_GPG = True
671
                self.gpg = gpg.GnuPG(gajim.config.get('use_gpg_agent'))
672 673 674 675 676 677
            self.connect_and_init(show, msg, sign_msg)
            return

        if show == 'offline':
            self.connected = 0
            if self.connection:
678
                p = nbxmpp.Presence(typ = 'unavailable')
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
                p = self.add_sha(p, False)
                if msg:
                    p.setStatus(msg)

                self.connection.RegisterDisconnectHandler(self._on_disconnected)
                self.connection.send(p, now=True)
                self.connection.start_disconnect()
            else:
                self._on_disconnected()
            return

        if show != 'offline' and self.connected > 0:
            # dont'try to connect, when we are in state 'connecting'
            if self.connected == 1:
                return
            if show == 'invisible':
                self._change_to_invisible(msg)
                return
            if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
                return -1
            was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
            self.connected = gajim.SHOW_LIST.index(show)
            if was_invisible:
                self._change_from_invisible()
            self._update_status(show, msg)
704 705

class Connection(CommonConnection, ConnectionHandlers):
706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
    def __init__(self, name):
        CommonConnection.__init__(self, name)
        ConnectionHandlers.__init__(self)
        # this property is used to prevent double connections
        self.last_connection = None # last ClientCommon instance
        # If we succeed to connect, remember it so next time we try (after a
        # disconnection) we try only this type.
        self.last_connection_type = None
        self.lang = None
        if locale.getdefaultlocale()[0]:
            self.lang = locale.getdefaultlocale()[0].split('_')[0]
        # increase/decrease default timeout for server responses
        self.try_connecting_for_foo_secs = 45
        # holds the actual hostname to which we are connected
        self.connected_hostname = None
721
        self.redirected = None
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
        self.last_time_to_reconnect = None
        self.new_account_info = None
        self.new_account_form = None
        self.annotations = {}
        self.last_io = gajim.idlequeue.current_time()
        self.last_sent = []
        self.last_history_time = {}
        self.password = passwords.get_password(name)

        self.music_track_info = 0
        self.location_info = {}
        self.pubsub_supported = False
        self.pubsub_publish_options_supported = False
        # Do we auto accept insecure connection
        self.connection_auto_accepted = False
        self.pasword_callback = None

        self.on_connect_success = None
        self.on_connect_failure = None
        self.retrycount = 0
        self.jids_for_auto_auth = [] # list of jid to auto-authorize
        self.available_transports = {} # list of available transports on this
        # server {'icq': ['icq.server.com', 'icq2.server.com'], }
        self.private_storage_supported = True
746
        self.privacy_rules_requested = False
747 748
        self.streamError = ''
        self.secret_hmac = str(random.random())[2:]
749 750 751

        self.sm = Smacks(self) # Stream Management

752 753
        gajim.ged.register_event_handler('privacy-list-received', ged.CORE,
            self._nec_privacy_list_received)
754 755 756 757
        gajim.ged.register_event_handler('agent-info-error-received', ged.CORE,
            self._nec_agent_info_error_received)
        gajim.ged.register_event_handler('agent-info-received', ged.CORE,
            self._nec_agent_info_received)
758 759
        gajim.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
            self._nec_message_outgoing)
760 761
        gajim.ged.register_event_handler('gc-message-outgoing', ged.OUT_CORE,
            self._nec_gc_message_outgoing)
762 763
    # END __init__

764 765
    def cleanup(self):
        ConnectionHandlers.cleanup(self)
766 767
        gajim.ged.remove_event_handler('privacy-list-received', ged.CORE,
            self._nec_privacy_list_received)
768 769
        gajim.ged.remove_event_handler('agent-info-error-received', ged.CORE,
            self._nec_agent_info_error_received)
770 771
        gajim.ged.remove_event_handler('agent-info-received', ged.CORE,
            self._nec_agent_info_received)
772 773
        gajim.ged.remove_event_handler('message-outgoing', ged.OUT_CORE,
            self._nec_message_outgoing)
774 775
        gajim.ged.remove_event_handler('message-outgoing', ged.OUT_CORE,
            self._nec_gc_message_outgoing)
776

777 778 779 780 781 782 783 784 785 786 787
    def get_config_values_or_default(self):
        if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
            self.keepalives = gajim.config.get_per('accounts', self.name,
                    'keep_alive_every_foo_secs')
        else:
            self.keepalives = 0
        if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'):
            self.pingalives = gajim.config.get_per('accounts', self.name,
                    'ping_alive_every_foo_secs')
        else:
            self.pingalives = 0
788 789
        self.client_cert = gajim.config.get_per('accounts', self.name,
            'client_cert')
790
        self.client_cert_passphrase = ''
791 792 793 794 795 796 797 798 799 800

    def check_jid(self, jid):
        return helpers.parse_jid(jid)

    def _reconnect(self):
        # Do not try to reco while we are already trying
        self.time_to_reconnect = None
        if self.connected < 2: # connection failed
            log.debug('reconnect')
            self.connected = 1
801 802
            gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
                show='connecting'))
803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
            self.retrycount += 1
            self.on_connect_auth = self._discover_server_at_connection
            self.connect_and_init(self.old_show, self.status, self.USE_GPG)
        else:
            # reconnect succeeded
            self.time_to_reconnect = None
            self.retrycount = 0

    # We are doing disconnect at so many places, better use one function in all
    def disconnect(self, on_purpose=False):
        gajim.interface.music_track_changed(None, None, self.name)
        self.reset_awaiting_pep()
        self.on_purpose = on_purpose
        self.connected = 0
        self.time_to_reconnect = None
        self.privacy_rules_supported = False
819 820
        if on_purpose:
            self.sm = Smacks(self)
821 822 823 824 825 826 827 828
        if self.connection:
            # make sure previous connection is completely closed
            gajim.proxy65_manager.disconnect(self.connection)
            self.terminate_sessions()
            self.remove_all_transfers()
            self.connection.disconnect()
            self.last_connection = None
            self.connection = None
zimio's avatar
zimio committed
829 830 831 832 833 834 835 836 837
    def set_oldst(self): # Set old state
        if self.old_show:
            self.connected = gajim.SHOW_LIST.index(self.old_show)
            gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
                                           show=self.connected))
        else: # we default to online
            self.connected = 2
            gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
                                    show=gajim.SHOW_LIST[self.connected]))
838 839 840 841 842 843 844 845 846 847 848 849

    def _disconnectedReconnCB(self):
        """
        Called when we are disconnected
        """
        log.info('disconnectedReconnCB called')
        if gajim.account_is_connected(self.name):
            # we cannot change our status to offline or connecting
            # after we auth to server
            self.old_show = gajim.SHOW_LIST[self.connected]
        self.connected = 0
        if not self.on_purpose:
zimio's avatar
zimio committed
850 851
            if not (self.sm and self.sm.resumption):
                gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
852
                    show='offline'))
853
            else:
854
                self.sm.enabled = False
855 856
                gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
                    show='error'))
857 858 859
            self.disconnect()
            if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
                self.connected = -1
860 861
                gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
                    show='error'))
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
                if gajim.status_before_autoaway[self.name]:
                    # We were auto away. So go back online
                    self.status = gajim.status_before_autoaway[self.name]
                    gajim.status_before_autoaway[self.name] = ''
                    self.old_show = 'online'
                # this check has moved from _reconnect method
                # do exponential backoff until 15 minutes,
                # then small linear increase
                if self.retrycount < 2 or self.last_time_to_reconnect is None:
                    self.last_time_to_reconnect = 5
                if self.last_time_to_reconnect < 800:
                    self.last_time_to_reconnect *= 1.5
                self.last_time_to_reconnect += randomsource.randint(0, 5)
                self.time_to_reconnect = int(self.last_time_to_reconnect)
                log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect)
                gajim.idlequeue.set_alarm(self._reconnect_alarm,
                        self.time_to_reconnect)
            elif self.on_connect_failure:
                self.on_connect_failure()
                self.on_connect_failure = None
            else:
                # show error dialog
                self._connection_lost()
        else:
886 887 888 889 890 891
            if self.redirected:
                self.disconnect(on_purpose=True)
                self.connect()
                return
            else:
                self.disconnect()
892 893 894 895 896 897
        self.on_purpose = False
    # END disconnectedReconnCB

    def _connection_lost(self):
        log.debug('_connection_lost')
        self.disconnect(on_purpose = False)
898 899 900
        gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
            title=_('Connection with account "%s" has been lost') % self.name,
            msg=_('Reconnect manually.')))
901 902 903

    def _event_dispatcher(self, realm, event, data):
        CommonConnection._event_dispatcher(self, realm, event, data)
904 905
        if realm == nbxmpp.NS_REGISTER:
            if event == nbxmpp.features_nb.REGISTER_DATA_RECEIVED:
906 907 908 909 910
                # data is (agent, DataFrom, is_form, error_msg)
                if self.new_account_info and \
                self.new_account_info['hostname'] == data[0]:
                    # it's a new account
                    if not data[1]: # wrong answer
911 912 913
                        reason = _('Server %(name)s answered wrongly to '
                            'register request: %(error)s') % {'name': data[0],
                            'error': data[3]}
914 915
                        gajim.nec.push_incoming_event(AccountNotCreatedEvent(
                            None, conn=self, reason=reason))
916 917 918
                        return
                    is_form = data[2]
                    conf = data[1]
919 920
                    if data[4] is not '':
                        helpers.replace_dataform_media(conf, data[4])
921 922
                    if self.new_account_form:
                        def _on_register_result(result):
923
                            if not nbxmpp.isResultNode(result):
924
                                gajim.nec.push_incoming_event(AccountNotCreatedEvent(
925
                                    None, conn=self, reason=result.getError()))
926 927 928
                                return
                            if gajim.HAVE_GPG:
                                self.USE_GPG = True
929
                                self.gpg = gpg.GnuPG(gajim.config.get(
930
                                        'use_gpg_agent'))
931 932 933
                            gajim.nec.push_incoming_event(
                                AccountCreatedEvent(None, conn=self,
                                account_info = self.new_account_info))
934 935 936 937 938 939 940 941 942 943
                            self.new_account_info = None
                            self.new_account_form = None
                            if self.connection:
                                self.connection.UnregisterDisconnectHandler(
                                        self._on_new_account)
                            self.disconnect(on_purpose=True)
                        # it's the second time we get the form, we have info user
                        # typed, so send them
                        if is_form:
                            #TODO: Check if form has changed
944
                            iq = nbxmpp.Iq('set', nbxmpp.NS_REGISTER,
945
                                to=self._hostname)
946 947 948 949 950 951 952
                            iq.setTag('query').addChild(node=self.new_account_form)
                            self.connection.SendAndCallForResponse(iq,
                                    _on_register_result)
                        else:
                            if self.new_account_form.keys().sort() != \
                            conf.keys().sort():
                                # requested config has changed since first connection
953 954
                                reason = _('Server %s provided a different '
                                    'registration form') % data[0]
955
                                gajim.nec.push_incoming_event(AccountNotCreatedEvent(
956
                                    None, conn=self, reason=reason))
957
                                return
958
                            nbxmpp.features_nb.register(self.connection,
959 960 961
                                    self._hostname, self.new_account_form,
                                    _on_register_result)
                        return
962 963
                    gajim.nec.push_incoming_event(NewAccountConnectedEvent(None,
                        conn=self, config=conf, is_form=is_form))
964
                    self.connection.UnregisterDisconnectHandler(
965
                        self._on_new_account)
966 967 968
                    self.disconnect(on_purpose=True)
                    return
                if not data[1]: # wrong answer
969 970 971 972
                    gajim.nec.push_incoming_event(InformationEvent(None,
                        conn=self, level='error', pri_txt=_('Invalid answer'),
                        sec_txt=_('Transport %(name)s answered wrongly to '
                        'register request: %(error)s') % {'name': data[0],
973
                        'error': data[3]}))
974 975 976
                    return
                is_form = data[2]
                conf = data[1]
977 978 979
                gajim.nec.push_incoming_event(RegisterAgentInfoReceivedEvent(
                    None, conn=self, agent=data[0], config=conf,
                    is_form=is_form))
980 981
        elif realm == nbxmpp.NS_PRIVACY:
            if event == nbxmpp.features_nb.PRIVACY_LISTS_RECEIVED:
982
                # data is (list)
983 984
                gajim.nec.push_incoming_event(PrivacyListsReceivedEvent(None,
                    conn=self, lists_list=data))
985
            elif event == nbxmpp.features_nb.PRIVACY_LIST_RECEIVED:
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004
                # data is (resp)
                if not data:
                    return
                rules = []
                name = data.getTag('query').getTag('list').getAttr('name')
                for child in data.getTag('query').getTag('list').getChildren():
                    dict_item = child.getAttrs()
                    childs = []
                    if 'type' in dict_item:
                        for scnd_child in child.getChildren():
                            childs += [scnd_child.getName()]
                        rules.append({'action':dict_item['action'],
                                'type':dict_item['type'], 'order':dict_item['order'],
                                'value':dict_item['value'], 'child':childs})
                    else:
                        for scnd_child in child.getChildren():
                            childs.append(scnd_child.getName())
                        rules.append({'action':dict_item['action'],
                                'order':dict_item['order'], 'child':childs})
1005 1006
                gajim.nec.push_incoming_event(PrivacyListReceivedEvent(None,
                    conn=self, list_name=name, rules=rules))
1007
            elif event == nbxmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
1008
                # data is (dict)
1009 1010 1011
                gajim.nec.push_incoming_event(PrivacyListActiveDefaultEvent(
                    None, conn=self, active_list=data['active'],
                    default_list=data['default']))
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038

    def _select_next_host(self, hosts):
        """
        Selects the next host according to RFC2782 p.3 based on it's priority.
        Chooses between hosts with the same priority randomly, where the
        probability of being selected is proportional to the weight of the host
        """
        hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio'))

        try:
            lowest_prio = hosts_by_prio[0]['prio']
        except IndexError:
            raise ValueError("No hosts to choose from!")

        hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio]

        if len(hosts_lowest_prio) == 1:
            return hosts_lowest_prio[0]
        else:
            rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio))
            weightsum = 0
            for host in sorted(hosts_lowest_prio, key=operator.itemgetter(
            'weight')):
                weightsum += host['weight']
                if weightsum >= rndint:
                    return host

1039
    def connect(self, data=None):
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
        """
        Start a connection to the Jabber server

        Returns connection, and connection type ('tls', 'ssl', 'plain', '') data
        MUST contain hostname, usessl, proxy, use_custom_host, custom_host (if
        use_custom_host), custom_port (if use_custom_host)
        """
        if self.connection:
            return self.connection, ''

zimio's avatar
zimio committed
1050
        if self.sm.resuming and self.sm.location:
1051
            # If resuming and server gave a location, connect from there
zimio's avatar
zimio committed
1052 1053
            hostname = self.sm.location
            self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
1054
                self.name, 'try_connecting_for_foo_secs')
zimio's avatar
zimio committed
1055
            use_custom = False
1056
            proxy = helpers.get_proxy_info(self.name)
1057

zimio's avatar
zimio committed
1058
        elif data:
1059 1060 1061
            hostname = data['hostname']
            self.try_connecting_for_foo_secs = 45
            p = data['proxy']
1062 1063 1064 1065
            if p and p in gajim.config.get_per('proxies'):
                proxy = {}
                proxyptr = gajim.config.get_per('proxies', p)
                for key in proxyptr.keys():
1066
                    proxy[key] = proxyptr[key]
1067 1068
            else:
                proxy = None
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078
            use_srv = True
            use_custom = data['use_custom_host']
            if use_custom:
                custom_h = data['custom_host']
                custom_p = data['custom_port']
        else:
            hostname = gajim.config.get_per('accounts', self.name, 'hostname')
            usessl = gajim.config.get_per('accounts', self.name, 'usessl')
            self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
                    self.name, 'try_connecting_for_foo_secs')
1079
            proxy = helpers.get_proxy_info(self.name)
1080
            use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
1081 1082 1083 1084 1085 1086
            if self.redirected:
                use_custom = True
                custom_h = self.redirected['host']
                custom_p = self.redirected['port']
            else:
                use_custom = gajim.config.get_per('accounts', self.name,
1087
                    'use_custom_host')
1088 1089 1090 1091
                custom_h = gajim.config.get_per('accounts', self.name,
                    'custom_host')
                custom_p = gajim.config.get_per('accounts', self.name,
                    'custom_port')
1092 1093 1094

        # create connection if it doesn't already exist
        self.connected = 1
1095

1096 1097 1098 1099 1100 1101 1102
        h = hostname
        p = 5222
        ssl_p = 5223
        if use_custom:
            h = custom_h
            p = custom_p
            ssl_p = custom_p
1103 1104
            if not self.redirected:
                use_srv = False
1105

1106
        self.redirected = None
1107 1108 1109 1110 1111 1112 1113 1114
        # SRV resolver
        self._proxy = proxy
        self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
                'weight': 10} ]
        self._hostname = hostname
        if use_srv:
            # add request for srv query to the resolve, on result '_on_resolve'
            # will be called
1115 1116
            gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(
                h), self._on_resolve)
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126
        else:
            self._on_resolve('', [])

    def _on_resolve(self, host, result_array):
        # SRV query returned at least one valid result, we put it in hosts dict
        if len(result_array) != 0:
            self._hosts = [i for i in result_array]
            # Add ssl port
            ssl_p = 5223
            if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
1127 1128
                ssl_p = gajim.config.get_per('accounts', self.name,
                    'custom_port')
1129 1130 1131 1132 1133 1134 1135 1136 1137
            for i in self._hosts:
                i['ssl_port'] = ssl_p
        self._connect_to_next_host()


    def _connect_to_next_host(self, retry=False):
        log.debug('Connection to next host')
        if len(self._hosts):
            # No config option exist when creating a new account
1138
            if self.name in gajim.config.get_per('accounts'):
1139 1140 1141 1142
                self._connection_types = gajim.config.get_per('accounts', self.name,
                        'connection_types').split()
            else:
                self._connection_types = ['tls', 'ssl', 'plain']
1143 1144 1145 1146 1147
            if self.last_connection_type:
                if self.last_connection_type in self._connection_types:
                    self._connection_types.remove(self.last_connection_type)
                self._connection_types.insert(0, self.last_connection_type)

1148 1149 1150 1151

            if self._proxy and self._proxy['type']=='bosh':
                # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain"
                # connection and TLS with handshake right after TCP connecting ("ssl")
1152
                scheme = nbxmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
                if scheme=='https':
                    self._connection_types = ['ssl']
                else:
                    self._connection_types = ['plain']

            host = self._select_next_host(self._hosts)
            self._current_host = host
            self._hosts.remove(host)
            self.connect_to_next_type()

        else:
            if not retry and self.retrycount == 0:
                log.debug("Out of hosts, giving up connecting to %s", self.name)
                self.time_to_reconnect = None
                if self.on_connect_failure:
                    self.on_connect_failure()
                    self.on_connect_failure = None
                else:
                    # shown error dialog
                    self._connection_lost()
            else:
                # try reconnect if connection has failed before auth to server
                self._disconnectedReconnCB()

    def connect_to_next_type(self, retry=False):
1178 1179 1180 1181
        if self.redirected:
            self.disconnect(on_purpose=True)
            self.connect()
            return
1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204
        if len(self._connection_types):
            self._current_type = self._connection_types.pop(0)
            if self.last_connection:
                self.last_connection.socket.disconnect()
                self.last_connection = None
                self.connection = None

            if self._current_type == 'ssl':
                # SSL (force TLS on different port than plain)
                # If we do TLS over BOSH, port of XMPP server should be the standard one
                # and TLS should be negotiated because TLS on 5223 is deprecated
                if self._proxy and self._proxy['type']=='bosh':
                    port = self._current_host['port']
                else:
                    port = self._current_host['ssl_port']
            elif self._current_type == 'tls':
                # TLS - negotiate tls after XMPP stream is estabilished
                port = self._current_host['port']
            elif self._current_type == 'plain':
                # plain connection on defined port
                port = self._current_host['port']

            cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
1205