Newer
Older
## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
## Travis Shirk <travis AT pobox.com>
## Nikos Kouremenos <kourem AT gmail.com>
## 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>
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.

Yann Leboulanger
committed
import random

Yann Leboulanger
committed
import locale
try:
randomsource = random.SystemRandom()
randomsource = random.Random()
randomsource.seed()

Yann Leboulanger
committed
import signal
if os.name != 'nt':
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
from common import gajim
from common import GnuPG
from common import passwords
from connection_handlers import *
log = logging.getLogger('gajim.c.connection')
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"),
18: _("Self signed certificate"),
19: _("Self signed certificate in certificate chain"),
20: _("Unable to get local issuer certificate"),
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")
class Connection(ConnectionHandlers):

Yann Leboulanger
committed
def __init__(self, name):
# self.connected:
# 0=>offline,
# 1=>connection in progress,
# 2=>authorised
self.connected = 0
self.connection = None # xmpppy ClientCommon instance
# this property is used to prevent double connections
self.last_connection = None # last ClientCommon instance

Yann Leboulanger
committed
# 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

Yann Leboulanger
committed
self.lang = None
if locale.getdefaultlocale()[0]:
self.lang = locale.getdefaultlocale()[0].split('_')[0]
self.USE_GPG = False
if gajim.HAVE_GPG:
self.USE_GPG = True
self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
self.priority = gajim.get_priority(name, 'offline')
# 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
self.last_time_to_reconnect = None

nkour
committed
self.bookmarks = []
self.annotations = {}
self.on_purpose = False
self.last_io = gajim.idlequeue.current_time()
self.last_sent = []
self.last_history_time = {}
self.password = passwords.get_password(name)
self.server_resource = gajim.config.get_per('accounts', name, 'resource')
# All valid resource substitution strings should be added to this hash.
if self.server_resource:
self.server_resource = Template(self.server_resource).safe_substitute({
'hostname': socket.gethostname()
})
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')
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
# Used to ask privacy only once at connection
self.privacy_rules_requested = False
self.blocked_list = []
self.blocked_contacts = []
self.blocked_groups = []
self.blocked_all = False
self.music_track_info = 0
self.pubsub_supported = False
self.pubsub_publish_options_supported = False
self.mood = {}
self.tune = {}
self.activity = {}
# Do we continue connection when we get roster (send presence,get vcard..)

Yann Leboulanger
committed
# Do we auto accept insecure connection
self.connection_auto_accepted = False
# 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}

Yann Leboulanger
committed
self.pasword_callback = None
self.on_connect_failure = None
self.jids_for_auto_auth = [] # list of jid to auto-authorize

Yann Leboulanger
committed
self.muc_jid = {} # jid of muc server for each transport type

Yann Leboulanger
committed
self.available_transports = {} # list of available transports on this
# server {'icq': ['icq.server.com', 'icq2.server.com'], }
self.vcard_supported = False
self.streamError = ''
if ev[0] in gajim.handlers:
log.debug('Sending %s event to GUI: %s' % (ev[0], ev[1:]))

Yann Leboulanger
committed
def dispatch(self, event, data):
'''always passes account name as first param'''
# Do not try to reco while we are already trying
self.time_to_reconnect = None

Yann Leboulanger
committed
if self.connected < 2: # connection failed
log.debug('reconnect')
self.on_connect_auth = self._discover_server_at_connection
self.connect_and_init(self.old_show, self.status, self.USE_GPG)
self.time_to_reconnect = None
self.retrycount = 0
# We are doing disconnect at so many places, better use one function in all

Yann Leboulanger
committed
gajim.interface.music_track_changed(None, None, self.name)
self.on_purpose = on_purpose
self.connected = 0
self.time_to_reconnect = None
self.privacy_rules_supported = False
if self.connection:
# make sure previous connection is completely closed
gajim.proxy65_manager.disconnect(self.connection)
self.connection.disconnect()
log.info('disconnectedReconnCB called')
# we cannot change our status to offline or connecting
self.old_show = gajim.SHOW_LIST[self.connected]
self.connected = 0
if not self.on_purpose:
if gajim.config.get_per('accounts', self.name, 'autoreconnect'):

Yann Leboulanger
committed
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)

Yann Leboulanger
committed
gajim.idlequeue.set_alarm(self._reconnect_alarm,
self.time_to_reconnect)
elif self.on_connect_failure:
self.on_connect_failure()

Yann Leboulanger
committed
# show error dialog
self.on_purpose = False
# END disconenctedReconnCB

Yann Leboulanger
committed
log.debug('_connection_lost')
self.disconnect(on_purpose = False)
self.dispatch('STATUS', 'offline')

Yann Leboulanger
committed
self.dispatch('CONNECTION_LOST',
(_('Connection with account "%s" has been lost') % self.name,
_('Reconnect manually.')))
if realm == common.xmpp.NS_REGISTER:
if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:

Yann Leboulanger
committed
# data is (agent, DataFrom, is_form, error_msg)
self.new_account_info['hostname'] == data[0]:

Yann Leboulanger
committed
if not data[1]: # wrong answer
self.dispatch('ACC_NOT_OK', (
_('Server %(name)s answered wrongly to register request: '
'%(error)s') % {'name': data[0], 'error': data[3]}))

Yann Leboulanger
committed
return
if self.new_account_form:
def _on_register_result(result):
if not common.xmpp.isResultNode(result):
self.dispatch('ACC_NOT_OK', (result.getError()))
return
if gajim.HAVE_GPG:
self.USE_GPG = True
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
self.gpg = GnuPG.GnuPG(gajim.config.get(
'use_gpg_agent'))
self.dispatch('ACC_OK', (self.new_account_info))
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
iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname)
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
self.dispatch('ACC_NOT_OK', (_(
'Server %s provided a different registration form')\
% data[0]))
return
common.xmpp.features_nb.register(self.connection,
self._hostname, self.new_account_form,
_on_register_result)
return
try:
errnum = self.connection.Connection.ssl_errnum
except AttributeError:
errnum = -1 # we don't have an errnum
ssl_msg = ''
if errnum > 0:
ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum)
ssl_cert = ''
if hasattr(self.connection.Connection, 'ssl_cert_pem'):
ssl_cert = self.connection.Connection.ssl_cert_pem
ssl_fingerprint = ''
if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'):
ssl_fingerprint = \
self.connection.Connection.ssl_fingerprint_sha1
self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg,

Yann Leboulanger
committed
errnum, ssl_cert, ssl_fingerprint))
self.connection.UnregisterDisconnectHandler(
self._on_new_account)
self.disconnect(on_purpose=True)

Yann Leboulanger
committed
return

Yann Leboulanger
committed
if not data[1]: # wrong answer
self.dispatch('ERROR', (_('Invalid answer'),
_('Transport %(name)s answered wrongly to register request: '
'%(error)s') % {'name': data[0], 'error': data[3]}))

Yann Leboulanger
committed
return
self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form))
elif realm == common.xmpp.NS_PRIVACY:
if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED:
# data is (list)
self.dispatch('PRIVACY_LISTS_RECEIVED', (data))
elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED:
# 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})
self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules))
elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
# data is (dict)
self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
elif realm == '':
if event == common.xmpp.transports_nb.DATA_RECEIVED:
self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
elif event == common.xmpp.transports_nb.DATA_SENT:
self.dispatch('STANZA_SENT', unicode(data))
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

Yann Leboulanger
committed

Yann Leboulanger
committed
def connect(self, data = None):
''' 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)'''

Yann Leboulanger
committed
if data:
hostname = data['hostname']

Yann Leboulanger
committed
p = data['proxy']
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',

Yann Leboulanger
committed
self.name, 'try_connecting_for_foo_secs')
p = gajim.config.get_per('accounts', self.name, 'proxy')
use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
use_custom = gajim.config.get_per('accounts', self.name,
'use_custom_host')
custom_h = gajim.config.get_per('accounts', self.name, 'custom_host')
custom_p = gajim.config.get_per('accounts', self.name, 'custom_port')
proxyptr = gajim.config.get_per('proxies', p)
for key in proxyptr.keys():
proxy[key] = proxyptr[key][1]

Yann Leboulanger
committed
elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
try:
try:
env_http_proxy = os.environ['HTTP_PROXY']

Yann Leboulanger
committed
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
env_http_proxy = os.environ['http_proxy']
env_http_proxy = env_http_proxy.strip('"')
# Dispose of the http:// prefix
env_http_proxy = env_http_proxy.split('://')
env_http_proxy = env_http_proxy[len(env_http_proxy)-1]
env_http_proxy = env_http_proxy.split('@')
if len(env_http_proxy) == 2:
login = env_http_proxy[0].split(':')
addr = env_http_proxy[1].split(':')
else:
login = ['', '']
addr = env_http_proxy[0].split(':')
proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]}
if len(addr) == 2:
proxy['port'] = addr[1]
else:
proxy['port'] = 3128
if len(login) == 2:
proxy['password'] = login[1]
else:
proxy['password'] = u''

Yann Leboulanger
committed
proxy = None
else:
proxy = None

Yann Leboulanger
committed
ssl_p = 5223
# use_srv = False # wants ssl? disable srv lookup

Yann Leboulanger
committed
if use_custom:
h = custom_h
p = custom_p

Yann Leboulanger
committed
ssl_p = custom_p
use_srv = False

Yann Leboulanger
committed
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
gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h),
self._on_resolve)
def _on_resolve(self, host, result_array):
# SRV query returned at least one valid result, we put it in hosts dict

Yann Leboulanger
committed
# Add ssl port
ssl_p = 5223
if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port')
for i in self._hosts:
i['ssl_port'] = ssl_p
self._connect_to_next_host()

Yann Leboulanger
committed
def _connect_to_next_host(self, retry=False):

Yann Leboulanger
committed
log.debug('Connection to next host')

Yann Leboulanger
committed
if len(self._hosts):
# No config option exist when creating a new account

Yann Leboulanger
committed
if self.last_connection_type:
self._connection_types = [self.last_connection_type]

Yann Leboulanger
committed
elif self.name in gajim.config.get_per('accounts'):
self._connection_types = gajim.config.get_per('accounts', self.name,
'connection_types').split()
else:
self._connection_types = ['tls', 'ssl', 'plain']
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")
scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
if scheme=='https':
self._connection_types = ['ssl']
else:
self._connection_types = ['plain']
host = self._select_next_host(self._hosts)

Yann Leboulanger
committed
self._current_host = host
self._hosts.remove(host)
self.connect_to_next_type()

Yann Leboulanger
committed
if not retry and self.retrycount == 0:
log.debug("Out of hosts, giving up connecting to %s", self.name)
if self.on_connect_failure:
self.on_connect_failure()
# shown error dialog
self._connection_lost()

nkour
committed
# try reconnect if connection has failed before auth to server
def connect_to_next_type(self, retry=False):

Yann Leboulanger
committed
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

Yann Leboulanger
committed
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

Yann Leboulanger
committed
port = self._current_host['port']
cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
mycerts = common.gajim.MY_CACERTS
secure_tuple = (self._current_type, cacerts, mycerts)
con = common.xmpp.NonBlockingClient(
domain=self._hostname,
caller=self,
idlequeue=gajim.idlequeue)
self.last_connection = con
# increase default timeout for server responses
common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
# FIXME: this is a hack; need a better way
if self.on_connect_success == self._on_new_account:
con.RegisterDisconnectHandler(self._on_new_account)
self.log_hosttype_info(port)
con.connect(
hostname=self._current_host['host'],
port=port,
on_connect=self.on_connect_success,
on_proxy_failure=self.on_proxy_failure,
on_connect_failure=self.connect_to_next_type,
proxy=self._proxy,
secure_tuple = secure_tuple)

Yann Leboulanger
committed
else:

Yann Leboulanger
committed
def log_hosttype_info(self, port):
msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
self._current_host['host'], port, self._current_type)
log.info(msg)
if self._proxy:
msg = '>>>>>> '
if self._proxy['type']=='bosh':
msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri'])
if self._proxy['type'] in ['http','socks5'] or self._proxy['bosh_useproxy']:
msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port'])
log.info(msg)
def _connect_failure(self, con_type=None):
if not con_type:
# we are not retrying, and not conecting
if not self.retrycount and self.connected != 0:
self.disconnect(on_purpose = True)

Yann Leboulanger
committed
pritxt = _('Could not connect to "%s"') % self._hostname
sectxt = _('Check your connection or try again later.')
if self.streamError:

Yann Leboulanger
committed
# show error dialog
key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError
if key in common.xmpp.ERRORS:
sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2]
self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt)))
return
# show popup
self.dispatch('CONNECTION_LOST', (pritxt, sectxt))
def on_proxy_failure(self, reason):
log.error('Connection to proxy failed: %s' % reason)
self.time_to_reconnect = None
self.on_connect_failure = None
self.disconnect(on_purpose = True)
self.dispatch('STATUS', 'offline')
self.dispatch('CONNECTION_LOST',
(_('Connection to proxy failed'), reason))
def _connect_success(self, con, con_type):
if not self.connected: # We went offline during connecting process
# FIXME - not possible, maybe it was when we used threads
return

Yann Leboulanger
committed
_con_type = con_type
if _con_type != self._current_type:
log.info('Connecting to next type beacuse desired is %s and returned is %s'
% (self._current_type, _con_type))

Yann Leboulanger
committed
self.connect_to_next_type()
return
con.RegisterDisconnectHandler(self._on_disconnected)

Yann Leboulanger
committed
if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
'warn_when_plaintext_connection'):

Yann Leboulanger
committed
self.dispatch('PLAIN_CONNECTION', (con,))
return True
if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \
and gajim.config.get_per('accounts', self.name,

Yann Leboulanger
committed
'warn_when_insecure_ssl_connection') and \
not self.connection_auto_accepted:
# Pyopenssl is not used
self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type))
return True

Yann Leboulanger
committed
return self.connection_accepted(con, con_type)
def connection_accepted(self, con, con_type):

Yann Leboulanger
committed
if not con or not con.Connection:
self.disconnect(on_purpose=True)
self.dispatch('STATUS', 'offline')
self.dispatch('CONNECTION_LOST',
(_('Could not connect to account %s') % self.name,
_('Connection with account %s has been lost. Retry connecting.') % \
self.name))
return

Yann Leboulanger
committed
self.connection_auto_accepted = False
self.connected_hostname = self._current_host['host']

Yann Leboulanger
committed
con.UnregisterDisconnectHandler(self._on_disconnected)
con.RegisterDisconnectHandler(self._disconnectedReconnCB)

Yann Leboulanger
committed
log.debug('Connected to server %s:%s with %s' % (
self._current_host['host'], self._current_host['port'], con_type))

Yann Leboulanger
committed
self.last_connection_type = con_type
if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
name = None
else:
name = gajim.config.get_per('accounts', self.name, 'name')

Yann Leboulanger
committed
hostname = gajim.config.get_per('accounts', self.name, 'hostname')
errnum = con.Connection.ssl_errnum
except AttributeError:
errnum = -1 # we don't have an errnum
if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts',
self.name, 'ignore_ssl_errors'):
text = _('The authenticity of the %s certificate could be invalid.') %\
hostname
if errnum in ssl_error:
text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
else:
text += _('\nUnknown SSL error: %d') % errnum

Yann Leboulanger
committed
self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem,
con.Connection.ssl_fingerprint_sha1))
return True
if hasattr(con.Connection, 'ssl_fingerprint_sha1'):
saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1')
if saved_fingerprint:
# Check sha1 fingerprint
if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint:
self.dispatch('FINGERPRINT_ERROR',
(con.Connection.ssl_fingerprint_sha1,))
return True
else:
gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1',
con.Connection.ssl_fingerprint_sha1)
con.auth(
user=name,
password=self.password,
resource=self.server_resource,
sasl=1,
on_auth=self.__on_auth)

Yann Leboulanger
committed
if not self.connection:
self.disconnect(on_purpose=True)
self.dispatch('STATUS', 'offline')
self.dispatch('CONNECTION_LOST',
(_('Could not connect to account %s') % self.name,
_('Connection with account %s has been lost. Retry connecting.') % \
self.name))
return
name = gajim.config.get_per('accounts', self.name, 'name')
self._register_handlers(self.connection, 'ssl')

Yann Leboulanger
committed
self.connection.auth(name, self.password, self.server_resource, 1,
self.__on_auth)
def _register_handlers(self, con, con_type):
self.peerhost = con.get_peerhost()
# notify the gui about con_type
self.dispatch('CON_TYPE', con_type)
ConnectionHandlers._register_handlers(self, con, con_type)
def __on_auth(self, con, auth):
if not con:

Yann Leboulanger
committed
self.disconnect(on_purpose=True)

Yann Leboulanger
committed
self.dispatch('STATUS', 'offline')

Yann Leboulanger
committed
self.dispatch('CONNECTION_LOST',
(_('Could not connect to "%s"') % self._hostname,
_('Check your connection or try again later')))
if self.on_connect_auth:
self.on_connect_auth(None)
self.on_connect_auth = None
return

Yann Leboulanger
committed
if not self.connected: # We went offline during connecting process
if self.on_connect_auth:
self.on_connect_auth(None)
self.on_connect_auth = None
return
if hasattr(con, 'Resource'):
self.server_resource = con.Resource
if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
# Get jid given by server
gajim.config.set_per('accounts', self.name, 'name', con.User)

Yann Leboulanger
committed
if auth:
self.last_io = gajim.idlequeue.current_time()
self.retrycount = 0
if self.on_connect_auth:
self.on_connect_auth(con)
self.on_connect_auth = None
# Forget password, it's wrong
self.password = None
gajim.log.debug("Couldn't authenticate to %s" % self._hostname)
self.disconnect(on_purpose = True)
self.dispatch('ERROR', (_('Authentication failed with "%s"') % \
self._hostname,
_('Please check your login and password for correctness.')))
if self.on_connect_auth:
self.on_connect_auth(None)
self.on_connect_auth = None
# END connect
if kill_core and gajim.account_is_connected(self.name):

Yann Leboulanger
committed
def add_lang(self, stanza):
if self.lang:
stanza.setAttr('xml:lang', self.lang)
def get_privacy_lists(self):
if not self.connection:
return
common.xmpp.features_nb.getPrivacyLists(self.connection)

Yann Leboulanger
committed
def send_keepalive(self):
# nothing received for the last foo seconds
if self.connection:
self.connection.send(' ')

Yann Leboulanger
committed
def sendPing(self, pingTo=None):
'''Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent
to server to detect connection failure at application level.'''

Yann Leboulanger
committed
if pingTo:
to = pingTo.get_full_jid()
self.dispatch('PING_SENT', (pingTo))
else:
to = gajim.config.get_per('accounts', self.name, 'hostname')

Yann Leboulanger
committed
iq = common.xmpp.Iq('get', to=to)
iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING)
if not common.xmpp.isResultNode(resp):
self.dispatch('PING_ERROR', (pingTo))
return
timeDiff = round(timePong - timePing,2)
self.dispatch('PING_REPLY', (pingTo, timeDiff))

Yann Leboulanger
committed
if pingTo:
timePing = time_time()
self.connection.SendAndCallForResponse(iq, _on_response)
else:
self.connection.send(iq)
gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per(
'accounts', self.name, 'time_for_ping_alive_answer'))
def get_active_default_lists(self):
if not self.connection:
return
common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
def del_privacy_list(self, privacy_list):
if not self.connection:
return

Yann Leboulanger
committed
def _on_del_privacy_list_result(result):
if result:
self.dispatch('PRIVACY_LIST_REMOVED', privacy_list)
else:
self.dispatch('ERROR', (_('Error while removing privacy list'),
_('Privacy list %s has not been removed. It is maybe active in '
'one of your connected resources. Deactivate it and try '

Yann Leboulanger
committed
'again.') % privacy_list))
common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list,
_on_del_privacy_list_result)
def get_privacy_list(self, title):
if not self.connection:
return
common.xmpp.features_nb.getPrivacyList(self.connection, title)
def set_privacy_list(self, listname, tags):
if not self.connection:
return
common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags)
def set_active_list(self, listname):
if not self.connection:
return
common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active')
def set_default_list(self, listname):
if not self.connection:
return
common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)

Yann Leboulanger
committed
def build_privacy_rule(self, name, action, order=1):

Yann Leboulanger
committed
'''Build a Privacy rule stanza for invisibility'''
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
l = iq.getTag('query').setTag('list', {'name': name})

Yann Leboulanger
committed
i = l.setTag('item', {'action': action, 'order': str(order)})

Yann Leboulanger
committed
i.setTag('presence-out')
return iq

Yann Leboulanger
committed
def build_invisible_rule(self):
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
l = iq.getTag('query').setTag('list', {'name': 'invisible'})
if self.name in gajim.interface.status_sent_to_groups and \
len(gajim.interface.status_sent_to_groups[self.name]) > 0:
for group in gajim.interface.status_sent_to_groups[self.name]:
i = l.setTag('item', {'type': 'group', 'value': group,
'action': 'allow', 'order': '1'})
i.setTag('presence-out')
if self.name in gajim.interface.status_sent_to_users and \
len(gajim.interface.status_sent_to_users[self.name]) > 0:
for jid in gajim.interface.status_sent_to_users[self.name]:
i = l.setTag('item', {'type': 'jid', 'value': jid,
'action': 'allow', 'order': '2'})
i.setTag('presence-out')
i = l.setTag('item', {'action': 'deny', 'order': '3'})
i.setTag('presence-out')
return iq
def set_invisible_rule(self):
if not gajim.account_is_connected(self.name):
return
iq = self.build_invisible_rule()
self.connection.send(iq)

Yann Leboulanger
committed
def activate_privacy_rule(self, name):

steve-e
committed
'''activate a privacy rule'''
if not self.connection:
return

Yann Leboulanger
committed
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
iq.getTag('query').setTag('active', {'name': name})
self.connection.send(iq)
def send_invisible_presence(self, msg, signed, initial = False):

Yann Leboulanger
committed
if not self.privacy_rules_supported:
self.dispatch('STATUS', gajim.SHOW_LIST[self.connected])
self.dispatch('ERROR', (_('Invisibility not supported'),
_('Account %s doesn\'t support invisibility.') % self.name))
return
# If we are already connected, and privacy rules are supported, send
# offline presence first as it's required by XEP-0126
if self.connected > 1 and self.privacy_rules_supported:
self.on_purpose = True
p = common.xmpp.Presence(typ = 'unavailable')
p = self.add_sha(p, False)
if msg:
p.setStatus(msg)
self.remove_all_transfers()
self.connection.send(p)

Yann Leboulanger
committed
# try to set the privacy rule

Yann Leboulanger
committed
iq = self.build_invisible_rule()

Yann Leboulanger
committed
self.connection.SendAndCallForResponse(iq, self._continue_invisible,
{'msg': msg, 'signed': signed, 'initial': initial})

Yann Leboulanger
committed
def _continue_invisible(self, con, iq_obj, msg, signed, initial):
if iq_obj.getType() == 'error': # server doesn't support privacy lists

Yann Leboulanger
committed
return
# active the privacy rule
self.privacy_rules_supported = True
self.activate_privacy_rule('invisible')
self.connected = gajim.SHOW_LIST.index('invisible')

Yann Leboulanger
committed
self.status = msg
priority = unicode(gajim.get_priority(self.name, 'invisible'))
p = common.xmpp.Presence(priority = priority)
p = self.add_sha(p, True)

Yann Leboulanger
committed
if msg:
p.setStatus(msg)
if signed:
p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
self.connection.send(p)
self.priority = priority

Yann Leboulanger
committed
self.dispatch('STATUS', 'invisible')

Yann Leboulanger
committed
if initial:
#ask our VCard
self.request_vcard(None)

Yann Leboulanger
committed
#Get bookmarks from private namespace
self.get_bookmarks()
#Get annotations
self.get_annotations()
#Inform GUI we just signed in

Yann Leboulanger
committed
def test_gpg_passphrase(self, password):
'''Returns 'ok', 'bad_pass' or 'expired' '''

Yann Leboulanger
committed
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'