gajim
view src/common/connection.py @ 10773:1076fc9700f5
merge elghinn's branch (roster versioning) to trunk. Fixes #4661, #3190
| author | Yann Leboulanger <asterix@lagaule.org> |
|---|---|
| date | Fri, 10 Jul 2009 15:05:01 +0200 |
| parents | 5f154c48e9db 06e57851eb8e |
| children | d40c9754b473 f0472ee0e7b7 |
line source
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-2008 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 ##
35 import os
36 import random
37 import socket
38 import operator
40 import time
41 import locale
43 try:
44 randomsource = random.SystemRandom()
45 except Exception:
46 randomsource = random.Random()
47 randomsource.seed()
49 import signal
50 if os.name != 'nt':
51 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
53 import common.xmpp
54 from common import helpers
55 from common import gajim
56 from common import GnuPG
57 from common import passwords
58 from common import exceptions
60 from connection_handlers import *
62 from string import Template
63 import logging
64 log = logging.getLogger('gajim.c.connection')
66 ssl_error = {
67 2: _("Unable to get issuer certificate"),
68 3: _("Unable to get certificate CRL"),
69 4: _("Unable to decrypt certificate's signature"),
70 5: _("Unable to decrypt CRL's signature"),
71 6: _("Unable to decode issuer public key"),
72 7: _("Certificate signature failure"),
73 8: _("CRL signature failure"),
74 9: _("Certificate is not yet valid"),
75 10: _("Certificate has expired"),
76 11: _("CRL is not yet valid"),
77 12: _("CRL has expired"),
78 13: _("Format error in certificate's notBefore field"),
79 14: _("Format error in certificate's notAfter field"),
80 15: _("Format error in CRL's lastUpdate field"),
81 16: _("Format error in CRL's nextUpdate field"),
82 17: _("Out of memory"),
83 18: _("Self signed certificate"),
84 19: _("Self signed certificate in certificate chain"),
85 20: _("Unable to get local issuer certificate"),
86 21: _("Unable to verify the first certificate"),
87 22: _("Certificate chain too long"),
88 23: _("Certificate revoked"),
89 24: _("Invalid CA certificate"),
90 25: _("Path length constraint exceeded"),
91 26: _("Unsupported certificate purpose"),
92 27: _("Certificate not trusted"),
93 28: _("Certificate rejected"),
94 29: _("Subject issuer mismatch"),
95 30: _("Authority and subject key identifier mismatch"),
96 31: _("Authority and issuer serial number mismatch"),
97 32: _("Key usage does not include certificate signing"),
98 50: _("Application verification failure")
99 }
100 class Connection(ConnectionHandlers):
101 '''Connection class'''
102 def __init__(self, name):
103 ConnectionHandlers.__init__(self)
104 self.name = name
105 # self.connected:
106 # 0=>offline,
107 # 1=>connection in progress,
108 # 2=>authorised
109 self.connected = 0
110 self.connection = None # xmpppy ClientCommon instance
111 # this property is used to prevent double connections
112 self.last_connection = None # last ClientCommon instance
113 # If we succeed to connect, remember it so next time we try (after a
114 # disconnection) we try only this type.
115 self.last_connection_type = None
116 self.lang = None
117 if locale.getdefaultlocale()[0]:
118 self.lang = locale.getdefaultlocale()[0].split('_')[0]
119 self.is_zeroconf = False
120 self.gpg = None
121 self.USE_GPG = False
122 if gajim.HAVE_GPG:
123 self.USE_GPG = True
124 self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
125 self.status = ''
126 self.priority = gajim.get_priority(name, 'offline')
127 self.old_show = ''
128 # increase/decrease default timeout for server responses
129 self.try_connecting_for_foo_secs = 45
130 # holds the actual hostname to which we are connected
131 self.connected_hostname = None
132 self.time_to_reconnect = None
133 self.last_time_to_reconnect = None
134 self.new_account_info = None
135 self.new_account_form = None
136 self.bookmarks = []
137 self.annotations = {}
138 self.on_purpose = False
139 self.last_io = gajim.idlequeue.current_time()
140 self.last_sent = []
141 self.last_history_time = {}
142 self.password = passwords.get_password(name)
143 self.server_resource = gajim.config.get_per('accounts', name, 'resource')
144 # All valid resource substitution strings should be added to this hash.
145 if self.server_resource:
146 self.server_resource = Template(self.server_resource).safe_substitute({
147 'hostname': socket.gethostname()
148 })
149 if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
150 self.keepalives = gajim.config.get_per('accounts', self.name,
151 'keep_alive_every_foo_secs')
152 else:
153 self.keepalives = 0
154 if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'):
155 self.pingalives = gajim.config.get_per('accounts', self.name,
156 'ping_alive_every_foo_secs')
157 else:
158 self.pingalives = 0
159 self.privacy_rules_supported = False
160 self.blocked_list = []
161 self.blocked_contacts = []
162 self.blocked_groups = []
163 self.blocked_all = False
164 self.music_track_info = 0
165 self.pubsub_supported = False
166 self.pep_supported = False
167 self.mood = {}
168 self.tune = {}
169 self.activity = {}
170 # Do we continue connection when we get roster (send presence,get vcard..)
171 self.continue_connect_info = None
172 # Do we auto accept insecure connection
173 self.connection_auto_accepted = False
174 # To know the groupchat jid associated with a sranza ID. Useful to
175 # request vcard or os info... to a real JID but act as if it comes from
176 # the fake jid
177 self.groupchat_jids = {} # {ID : groupchat_jid}
178 self.pasword_callback = None
180 self.on_connect_success = None
181 self.on_connect_failure = None
182 self.retrycount = 0
183 self.jids_for_auto_auth = [] # list of jid to auto-authorize
184 self.muc_jid = {} # jid of muc server for each transport type
185 self.available_transports = {} # list of available transports on this
186 # server {'icq': ['icq.server.com', 'icq2.server.com'], }
187 self.vcard_supported = True
188 self.private_storage_supported = True
189 self.streamError = ''
190 # END __init__
192 def put_event(self, ev):
193 if ev[0] in gajim.handlers:
194 log.debug('Sending %s event to GUI: %s' % (ev[0], ev[1:]))
195 gajim.handlers[ev[0]](self.name, ev[1])
197 def dispatch(self, event, data):
198 '''always passes account name as first param'''
199 self.put_event((event, data))
202 def _reconnect(self):
203 # Do not try to reco while we are already trying
204 self.time_to_reconnect = None
205 if self.connected < 2: # connection failed
206 log.debug('reconnect')
207 self.connected = 1
208 self.dispatch('STATUS', 'connecting')
209 self.retrycount += 1
210 self.on_connect_auth = self._init_roster
211 self.connect_and_init(self.old_show, self.status, self.USE_GPG)
212 else:
213 # reconnect succeeded
214 self.time_to_reconnect = None
215 self.retrycount = 0
217 # We are doing disconnect at so many places, better use one function in all
218 def disconnect(self, on_purpose=False):
219 gajim.interface.roster.music_track_changed(None, None, self.name)
220 self.on_purpose = on_purpose
221 self.connected = 0
222 self.time_to_reconnect = None
223 self.privacy_rules_supported = False
224 if self.connection:
225 # make sure previous connection is completely closed
226 gajim.proxy65_manager.disconnect(self.connection)
227 self.connection.disconnect()
228 self.last_connection = None
229 self.connection = None
231 def _disconnectedReconnCB(self):
232 '''Called when we are disconnected'''
233 log.info('disconnectedReconnCB called')
234 if gajim.account_is_connected(self.name):
235 # we cannot change our status to offline or connecting
236 # after we auth to server
237 self.old_show = gajim.SHOW_LIST[self.connected]
238 self.connected = 0
239 if not self.on_purpose:
240 self.dispatch('STATUS', 'offline')
241 self.disconnect()
242 if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
243 self.connected = -1
244 self.dispatch('STATUS', 'error')
245 if gajim.status_before_autoaway[self.name]:
246 # We were auto away. So go back online
247 self.status = gajim.status_before_autoaway[self.name]
248 gajim.status_before_autoaway[self.name] = ''
249 self.old_show = 'online'
250 # this check has moved from _reconnect method
251 # do exponential backoff until 15 minutes,
252 # then small linear increase
253 if self.retrycount < 2 or self.last_time_to_reconnect is None:
254 self.last_time_to_reconnect = 5
255 if self.last_time_to_reconnect < 800:
256 self.last_time_to_reconnect *= 1.5
257 self.last_time_to_reconnect += randomsource.randint(0, 5)
258 self.time_to_reconnect = int(self.last_time_to_reconnect)
259 log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect)
260 gajim.idlequeue.set_alarm(self._reconnect_alarm,
261 self.time_to_reconnect)
262 elif self.on_connect_failure:
263 self.on_connect_failure()
264 self.on_connect_failure = None
265 else:
266 # show error dialog
267 self._connection_lost()
268 else:
269 self.disconnect()
270 self.on_purpose = False
271 # END disconenctedReconnCB
273 def _connection_lost(self):
274 log.debug('_connection_lost')
275 self.disconnect(on_purpose = False)
276 self.dispatch('STATUS', 'offline')
277 self.dispatch('CONNECTION_LOST',
278 (_('Connection with account "%s" has been lost') % self.name,
279 _('Reconnect manually.')))
281 def _event_dispatcher(self, realm, event, data):
282 if realm == common.xmpp.NS_REGISTER:
283 if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:
284 # data is (agent, DataFrom, is_form, error_msg)
285 if self.new_account_info and \
286 self.new_account_info['hostname'] == data[0]:
287 # it's a new account
288 if not data[1]: # wrong answer
289 self.dispatch('ACC_NOT_OK', (
290 _('Server %(name)s answered wrongly to register request: '
291 '%(error)s') % {'name': data[0], 'error': data[3]}))
292 return
293 is_form = data[2]
294 conf = data[1]
295 if self.new_account_form:
296 def _on_register_result(result):
297 if not common.xmpp.isResultNode(result):
298 self.dispatch('ACC_NOT_OK', (result.getError()))
299 return
300 if gajim.HAVE_GPG:
301 self.USE_GPG = True
302 self.gpg = GnuPG.GnuPG(gajim.config.get(
303 'use_gpg_agent'))
304 self.dispatch('ACC_OK', (self.new_account_info))
305 self.new_account_info = None
306 self.new_account_form = None
307 if self.connection:
308 self.connection.UnregisterDisconnectHandler(
309 self._on_new_account)
310 self.disconnect(on_purpose=True)
311 # it's the second time we get the form, we have info user
312 # typed, so send them
313 if is_form:
314 #TODO: Check if form has changed
315 iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname)
316 iq.setTag('query').addChild(node=self.new_account_form)
317 self.connection.SendAndCallForResponse(iq,
318 _on_register_result)
319 else:
320 if self.new_account_form.keys().sort() != \
321 conf.keys().sort():
322 # requested config has changed since first connection
323 self.dispatch('ACC_NOT_OK', (_(
324 'Server %s provided a different registration form')\
325 % data[0]))
326 return
327 common.xmpp.features_nb.register(self.connection,
328 self._hostname, self.new_account_form,
329 _on_register_result)
330 return
331 try:
332 errnum = self.connection.Connection.ssl_errnum
333 except AttributeError:
334 errnum = -1 # we don't have an errnum
335 ssl_msg = ''
336 if errnum > 0:
337 ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum)
338 ssl_cert = ''
339 if hasattr(self.connection.Connection, 'ssl_cert_pem'):
340 ssl_cert = self.connection.Connection.ssl_cert_pem
341 ssl_fingerprint = ''
342 if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'):
343 ssl_fingerprint = \
344 self.connection.Connection.ssl_fingerprint_sha1
345 self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg,
346 errnum, ssl_cert, ssl_fingerprint))
347 self.connection.UnregisterDisconnectHandler(
348 self._on_new_account)
349 self.disconnect(on_purpose=True)
350 return
351 if not data[1]: # wrong answer
352 self.dispatch('ERROR', (_('Invalid answer'),
353 _('Transport %(name)s answered wrongly to register request: '
354 '%(error)s') % {'name': data[0], 'error': data[3]}))
355 return
356 is_form = data[2]
357 conf = data[1]
358 self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form))
359 elif realm == common.xmpp.NS_PRIVACY:
360 if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED:
361 # data is (list)
362 self.dispatch('PRIVACY_LISTS_RECEIVED', (data))
363 elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED:
364 # data is (resp)
365 if not data:
366 return
367 rules = []
368 name = data.getTag('query').getTag('list').getAttr('name')
369 for child in data.getTag('query').getTag('list').getChildren():
370 dict_item = child.getAttrs()
371 childs = []
372 if 'type' in dict_item:
373 for scnd_child in child.getChildren():
374 childs += [scnd_child.getName()]
375 rules.append({'action':dict_item['action'],
376 'type':dict_item['type'], 'order':dict_item['order'],
377 'value':dict_item['value'], 'child':childs})
378 else:
379 for scnd_child in child.getChildren():
380 childs.append(scnd_child.getName())
381 rules.append({'action':dict_item['action'],
382 'order':dict_item['order'], 'child':childs})
383 self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules))
384 elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
385 # data is (dict)
386 self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
387 elif realm == '':
388 if event == common.xmpp.transports_nb.DATA_RECEIVED:
389 self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
390 elif event == common.xmpp.transports_nb.DATA_SENT:
391 self.dispatch('STANZA_SENT', unicode(data))
393 def _select_next_host(self, hosts):
394 '''Selects the next host according to RFC2782 p.3 based on it's
395 priority. Chooses between hosts with the same priority randomly,
396 where the probability of being selected is proportional to the weight
397 of the host.'''
399 hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio'))
401 try:
402 lowest_prio = hosts_by_prio[0]['prio']
403 except IndexError:
404 raise ValueError("No hosts to choose from!")
406 hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio]
408 if len(hosts_lowest_prio) == 1:
409 return hosts_lowest_prio[0]
410 else:
411 rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio))
412 weightsum = 0
413 for host in sorted(hosts_lowest_prio, key=operator.itemgetter(
414 'weight')):
415 weightsum += host['weight']
416 if weightsum >= rndint:
417 return host
419 def connect(self, data = None):
420 ''' Start a connection to the Jabber server.
421 Returns connection, and connection type ('tls', 'ssl', 'plain', '')
422 data MUST contain hostname, usessl, proxy, use_custom_host,
423 custom_host (if use_custom_host), custom_port (if use_custom_host)'''
424 if self.connection:
425 return self.connection, ''
427 if data:
428 hostname = data['hostname']
429 self.try_connecting_for_foo_secs = 45
430 p = data['proxy']
431 use_srv = True
432 use_custom = data['use_custom_host']
433 if use_custom:
434 custom_h = data['custom_host']
435 custom_p = data['custom_port']
436 else:
437 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
438 usessl = gajim.config.get_per('accounts', self.name, 'usessl')
439 self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
440 self.name, 'try_connecting_for_foo_secs')
441 p = gajim.config.get_per('accounts', self.name, 'proxy')
442 use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
443 use_custom = gajim.config.get_per('accounts', self.name,
444 'use_custom_host')
445 custom_h = gajim.config.get_per('accounts', self.name, 'custom_host')
446 custom_p = gajim.config.get_per('accounts', self.name, 'custom_port')
448 # create connection if it doesn't already exist
449 self.connected = 1
450 if p and p in gajim.config.get_per('proxies'):
451 proxy = {}
452 proxyptr = gajim.config.get_per('proxies', p)
453 for key in proxyptr.keys():
454 proxy[key] = proxyptr[key][1]
456 elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
457 try:
458 try:
459 env_http_proxy = os.environ['HTTP_PROXY']
460 except Exception:
461 env_http_proxy = os.environ['http_proxy']
462 env_http_proxy = env_http_proxy.strip('"')
463 # Dispose of the http:// prefix
464 env_http_proxy = env_http_proxy.split('://')
465 env_http_proxy = env_http_proxy[len(env_http_proxy)-1]
466 env_http_proxy = env_http_proxy.split('@')
468 if len(env_http_proxy) == 2:
469 login = env_http_proxy[0].split(':')
470 addr = env_http_proxy[1].split(':')
471 else:
472 login = ['', '']
473 addr = env_http_proxy[0].split(':')
475 proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]}
477 if len(addr) == 2:
478 proxy['port'] = addr[1]
479 else:
480 proxy['port'] = 3128
482 if len(login) == 2:
483 proxy['password'] = login[1]
484 else:
485 proxy['password'] = u''
487 except Exception:
488 proxy = None
489 else:
490 proxy = None
491 h = hostname
492 p = 5222
493 ssl_p = 5223
494 # use_srv = False # wants ssl? disable srv lookup
495 if use_custom:
496 h = custom_h
497 p = custom_p
498 ssl_p = custom_p
499 use_srv = False
501 # SRV resolver
502 self._proxy = proxy
503 self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
504 'weight': 10} ]
505 self._hostname = hostname
506 if use_srv:
507 # add request for srv query to the resolve, on result '_on_resolve'
508 # will be called
509 gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h),
510 self._on_resolve)
511 else:
512 self._on_resolve('', [])
514 def _on_resolve(self, host, result_array):
515 # SRV query returned at least one valid result, we put it in hosts dict
516 if len(result_array) != 0:
517 self._hosts = [i for i in result_array]
518 # Add ssl port
519 ssl_p = 5223
520 if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
521 ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port')
522 for i in self._hosts:
523 i['ssl_port'] = ssl_p
524 self._connect_to_next_host()
527 def _connect_to_next_host(self, retry=False):
528 log.debug('Connection to next host')
529 if len(self._hosts):
530 # No config option exist when creating a new account
531 if self.last_connection_type:
532 self._connection_types = [self.last_connection_type]
533 elif self.name in gajim.config.get_per('accounts'):
534 self._connection_types = gajim.config.get_per('accounts', self.name,
535 'connection_types').split()
536 else:
537 self._connection_types = ['tls', 'ssl', 'plain']
539 if self._proxy and self._proxy['type']=='bosh':
540 # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain"
541 # connection and TLS with handshake right after TCP connecting ("ssl")
542 scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
543 if scheme=='https':
544 self._connection_types = ['ssl']
545 else:
546 self._connection_types = ['plain']
548 host = self._select_next_host(self._hosts)
549 self._current_host = host
550 self._hosts.remove(host)
551 self.connect_to_next_type()
553 else:
554 if not retry and self.retrycount == 0:
555 log.debug("Out of hosts, giving up connecting to %s", self.name)
556 self.time_to_reconnect = None
557 if self.on_connect_failure:
558 self.on_connect_failure()
559 self.on_connect_failure = None
560 else:
561 # shown error dialog
562 self._connection_lost()
563 else:
564 # try reconnect if connection has failed before auth to server
565 self._disconnectedReconnCB()
567 def connect_to_next_type(self, retry=False):
568 if len(self._connection_types):
569 self._current_type = self._connection_types.pop(0)
570 if self.last_connection:
571 self.last_connection.socket.disconnect()
572 self.last_connection = None
573 self.connection = None
575 if self._current_type == 'ssl':
576 # SSL (force TLS on different port than plain)
577 # If we do TLS over BOSH, port of XMPP server should be the standard one
578 # and TLS should be negotiated because TLS on 5223 is deprecated
579 if self._proxy and self._proxy['type']=='bosh':
580 port = self._current_host['port']
581 else:
582 port = self._current_host['ssl_port']
583 elif self._current_type == 'tls':
584 # TLS - negotiate tls after XMPP stream is estabilished
585 port = self._current_host['port']
586 elif self._current_type == 'plain':
587 # plain connection on defined port
588 port = self._current_host['port']
590 cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
591 mycerts = common.gajim.MY_CACERTS
592 secure_tuple = (self._current_type, cacerts, mycerts)
594 con = common.xmpp.NonBlockingClient(
595 domain=self._hostname,
596 caller=self,
597 idlequeue=gajim.idlequeue)
599 self.last_connection = con
600 # increase default timeout for server responses
601 common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
602 # FIXME: this is a hack; need a better way
603 if self.on_connect_success == self._on_new_account:
604 con.RegisterDisconnectHandler(self._on_new_account)
606 self.log_hosttype_info(port)
607 con.connect(
608 hostname=self._current_host['host'],
609 port=port,
610 on_connect=self.on_connect_success,
611 on_proxy_failure=self.on_proxy_failure,
612 on_connect_failure=self.connect_to_next_type,
613 proxy=self._proxy,
614 secure_tuple = secure_tuple)
615 else:
616 self._connect_to_next_host(retry)
618 def log_hosttype_info(self, port):
619 msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
620 self._current_host['host'], port, self._current_type)
621 log.info(msg)
622 if self._proxy:
623 msg = '>>>>>> '
624 if self._proxy['type']=='bosh':
625 msg = '%s over BOSH %s:%s' % (msg, self._proxy['bosh_uri'], self._proxy['bosh_port'])
626 if self._proxy['type'] in ['http','socks5'] or self._proxy['bosh_useproxy']:
627 msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port'])
628 log.info(msg)
630 def _connect_failure(self, con_type=None):
631 if not con_type:
632 # we are not retrying, and not conecting
633 if not self.retrycount and self.connected != 0:
634 self.disconnect(on_purpose = True)
635 self.dispatch('STATUS', 'offline')
636 pritxt = _('Could not connect to "%s"') % self._hostname
637 sectxt = _('Check your connection or try again later.')
638 if self.streamError:
639 # show error dialog
640 key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError
641 if key in common.xmpp.ERRORS:
642 sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2]
643 self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt)))
644 return
645 # show popup
646 self.dispatch('CONNECTION_LOST', (pritxt, sectxt))
648 def on_proxy_failure(self, reason):
649 log.error('Connection to proxy failed: %s' % reason)
650 self.time_to_reconnect = None
651 self.on_connect_failure = None
652 self.disconnect(on_purpose = True)
653 self.dispatch('STATUS', 'offline')
654 self.dispatch('CONNECTION_LOST',
655 (_('Connection to proxy failed'), reason))
657 def _connect_success(self, con, con_type):
658 if not self.connected: # We went offline during connecting process
659 # FIXME - not possible, maybe it was when we used threads
660 return
661 _con_type = con_type
662 if _con_type != self._current_type:
663 log.info('Connecting to next type beacuse desired is %s and returned is %s'
664 % (self._current_type, _con_type))
665 self.connect_to_next_type()
666 return
667 con.RegisterDisconnectHandler(self._on_disconnected)
668 if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
669 'warn_when_plaintext_connection'):
670 self.dispatch('PLAIN_CONNECTION', (con,))
671 return True
672 if _con_type in ('tls', 'ssl') and not hasattr(con.Connection,
673 '_sslContext') and gajim.config.get_per('accounts', self.name,
674 'warn_when_insecure_ssl_connection') and \
675 not self.connection_auto_accepted:
676 # Pyopenssl is not used
677 self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type))
678 return True
679 return self.connection_accepted(con, con_type)
681 def connection_accepted(self, con, con_type):
682 if not con or not con.Connection:
683 self.disconnect(on_purpose=True)
684 self.dispatch('STATUS', 'offline')
685 self.dispatch('CONNECTION_LOST',
686 (_('Could not connect to account %s') % self.name,
687 _('Connection with account %s has been lost. Retry connecting.') % \
688 self.name))
689 return
690 self.hosts = []
691 self.connection_auto_accepted = False
692 self.connected_hostname = self._current_host['host']
693 self.on_connect_failure = None
694 con.UnregisterDisconnectHandler(self._on_disconnected)
695 con.RegisterDisconnectHandler(self._disconnectedReconnCB)
696 log.debug('Connected to server %s:%s with %s' % (
697 self._current_host['host'], self._current_host['port'], con_type))
699 self.last_connection_type = con_type
700 name = gajim.config.get_per('accounts', self.name, 'name')
701 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
702 self.connection = con
703 try:
704 errnum = con.Connection.ssl_errnum
705 except AttributeError:
706 errnum = -1 # we don't have an errnum
707 if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts',
708 self.name, 'ignore_ssl_errors'):
709 text = _('The authenticity of the %s certificate could be invalid.') %\
710 hostname
711 if errnum in ssl_error:
712 text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
713 else:
714 text += _('\nUnknown SSL error: %d') % errnum
715 self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem,
716 con.Connection.ssl_fingerprint_sha1))
717 return True
718 if hasattr(con.Connection, 'ssl_fingerprint_sha1'):
719 saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1')
720 if saved_fingerprint:
721 # Check sha1 fingerprint
722 if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint:
723 self.dispatch('FINGERPRINT_ERROR',
724 (con.Connection.ssl_fingerprint_sha1,))
725 return True
726 else:
727 gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1',
728 con.Connection.ssl_fingerprint_sha1)
729 self._register_handlers(con, con_type)
730 con.auth(
731 user=name,
732 password=self.password,
733 resource=self.server_resource,
734 sasl=1,
735 on_auth=self.__on_auth)
737 def ssl_certificate_accepted(self):
738 if not self.connection:
739 self.disconnect(on_purpose=True)
740 self.dispatch('STATUS', 'offline')
741 self.dispatch('CONNECTION_LOST',
742 (_('Could not connect to account %s') % self.name,
743 _('Connection with account %s has been lost. Retry connecting.') % \
744 self.name))
745 return
746 name = gajim.config.get_per('accounts', self.name, 'name')
747 self._register_handlers(self.connection, 'ssl')
748 self.connection.auth(name, self.password, self.server_resource, 1,
749 self.__on_auth)
751 def _register_handlers(self, con, con_type):
752 self.peerhost = con.get_peerhost()
753 # notify the gui about con_type
754 self.dispatch('CON_TYPE', con_type)
755 ConnectionHandlers._register_handlers(self, con, con_type)
757 def __on_auth(self, con, auth):
758 if not con:
759 self.disconnect(on_purpose=True)
760 self.dispatch('STATUS', 'offline')
761 self.dispatch('CONNECTION_LOST',
762 (_('Could not connect to "%s"') % self._hostname,
763 _('Check your connection or try again later')))
764 if self.on_connect_auth:
765 self.on_connect_auth(None)
766 self.on_connect_auth = None
767 return
768 if not self.connected: # We went offline during connecting process
769 if self.on_connect_auth:
770 self.on_connect_auth(None)
771 self.on_connect_auth = None
772 return
773 if hasattr(con, 'Resource'):
774 self.server_resource = con.Resource
775 if auth:
776 self.last_io = gajim.idlequeue.current_time()
777 self.connected = 2
778 self.retrycount = 0
779 if self.on_connect_auth:
780 self.on_connect_auth(con)
781 self.on_connect_auth = None
782 else:
783 # Forget password, it's wrong
784 self.password = None
785 gajim.log.debug("Couldn't authenticate to %s" % self._hostname)
786 self.disconnect(on_purpose = True)
787 self.dispatch('STATUS', 'offline')
788 self.dispatch('ERROR', (_('Authentication failed with "%s"') % \
789 self._hostname,
790 _('Please check your login and password for correctness.')))
791 if self.on_connect_auth:
792 self.on_connect_auth(None)
793 self.on_connect_auth = None
794 # END connect
796 def quit(self, kill_core):
797 if kill_core and gajim.account_is_connected(self.name):
798 self.disconnect(on_purpose=True)
800 def add_lang(self, stanza):
801 if self.lang:
802 stanza.setAttr('xml:lang', self.lang)
804 def get_privacy_lists(self):
805 if not self.connection:
806 return
807 common.xmpp.features_nb.getPrivacyLists(self.connection)
809 def send_keepalive(self):
810 # nothing received for the last foo seconds
811 if self.connection:
812 self.connection.send(' ')
814 def sendPing(self, pingTo=None):
815 '''Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent
816 to server to detect connection failure at application level.'''
817 if not self.connection:
818 return
819 id_ = self.connection.getAnID()
820 if pingTo:
821 to = pingTo.get_full_jid()
822 self.dispatch('PING_SENT', (pingTo))
823 else:
824 to = gajim.config.get_per('accounts', self.name, 'hostname')
825 self.awaiting_xmpp_ping_id = id_
826 iq = common.xmpp.Iq('get', to=to)
827 iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING)
828 iq.setID(id_)
829 def _on_response(resp):
830 timePong = time_time()
831 if not common.xmpp.isResultNode(resp):
832 self.dispatch('PING_ERROR', (pingTo))
833 return
834 timeDiff = round(timePong - timePing,2)
835 self.dispatch('PING_REPLY', (pingTo, timeDiff))
836 if pingTo:
837 timePing = time_time()
838 self.connection.SendAndCallForResponse(iq, _on_response)
839 else:
840 self.connection.send(iq)
841 gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per(
842 'accounts', self.name, 'time_for_ping_alive_answer'))
844 def get_active_default_lists(self):
845 if not self.connection:
846 return
847 common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
849 def del_privacy_list(self, privacy_list):
850 if not self.connection:
851 return
852 def _on_del_privacy_list_result(result):
853 if result:
854 self.dispatch('PRIVACY_LIST_REMOVED', privacy_list)
855 else:
856 self.dispatch('ERROR', (_('Error while removing privacy list'),
857 _('Privacy list %s has not been removed. It is maybe active in '
858 'one of your connected resources. Deactivate it and try '
859 'again.') % privacy_list))
860 common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list,
861 _on_del_privacy_list_result)
863 def get_privacy_list(self, title):
864 if not self.connection:
865 return
866 common.xmpp.features_nb.getPrivacyList(self.connection, title)
868 def set_privacy_list(self, listname, tags):
869 if not self.connection:
870 return
871 common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags)
873 def set_active_list(self, listname):
874 if not self.connection:
875 return
876 common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active')
878 def set_default_list(self, listname):
879 if not self.connection:
880 return
881 common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
883 def build_privacy_rule(self, name, action, order=1):
884 '''Build a Privacy rule stanza for invisibility'''
885 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
886 l = iq.getTag('query').setTag('list', {'name': name})
887 i = l.setTag('item', {'action': action, 'order': str(order)})
888 i.setTag('presence-out')
889 return iq
891 def build_invisible_rule(self):
892 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
893 l = iq.getTag('query').setTag('list', {'name': 'invisible'})
894 if self.name in gajim.interface.status_sent_to_groups and \
895 len(gajim.interface.status_sent_to_groups[self.name]) > 0:
896 for group in gajim.interface.status_sent_to_groups[self.name]:
897 i = l.setTag('item', {'type': 'group', 'value': group,
898 'action': 'allow', 'order': '1'})
899 i.setTag('presence-out')
900 if self.name in gajim.interface.status_sent_to_users and \
901 len(gajim.interface.status_sent_to_users[self.name]) > 0:
902 for jid in gajim.interface.status_sent_to_users[self.name]:
903 i = l.setTag('item', {'type': 'jid', 'value': jid,
904 'action': 'allow', 'order': '2'})
905 i.setTag('presence-out')
906 i = l.setTag('item', {'action': 'deny', 'order': '3'})
907 i.setTag('presence-out')
908 return iq
910 def set_invisible_rule(self):
911 if not gajim.account_is_connected(self.name):
912 return
913 iq = self.build_invisible_rule()
914 self.connection.send(iq)
916 def activate_privacy_rule(self, name):
917 '''activate a privacy rule'''
918 if not self.connection:
919 return
920 iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
921 iq.getTag('query').setTag('active', {'name': name})
922 self.connection.send(iq)
924 def send_invisible_presence(self, msg, signed, initial = False):
925 if not self.connection:
926 return
927 if not self.privacy_rules_supported:
928 self.dispatch('STATUS', gajim.SHOW_LIST[self.connected])
929 self.dispatch('ERROR', (_('Invisibility not supported'),
930 _('Account %s doesn\'t support invisibility.') % self.name))
931 return
932 # If we are already connected, and privacy rules are supported, send
933 # offline presence first as it's required by XEP-0126
934 if self.connected > 1 and self.privacy_rules_supported:
935 self.on_purpose = True
936 p = common.xmpp.Presence(typ = 'unavailable')
937 p = self.add_sha(p, False)
938 if msg:
939 p.setStatus(msg)
940 self.remove_all_transfers()
941 self.connection.send(p)
943 # try to set the privacy rule
944 iq = self.build_invisible_rule()
945 self.connection.SendAndCallForResponse(iq, self._continue_invisible,
946 {'msg': msg, 'signed': signed, 'initial': initial})
948 def _continue_invisible(self, con, iq_obj, msg, signed, initial):
949 if iq_obj.getType() == 'error': # server doesn't support privacy lists
950 return
951 # active the privacy rule
952 self.privacy_rules_supported = True
953 self.activate_privacy_rule('invisible')
954 self.connected = gajim.SHOW_LIST.index('invisible')
955 self.status = msg
956 priority = unicode(gajim.get_priority(self.name, 'invisible'))
957 p = common.xmpp.Presence(priority = priority)
958 p = self.add_sha(p, True)
959 if msg:
960 p.setStatus(msg)
961 if signed:
962 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
963 self.connection.send(p)
964 self.priority = priority
965 self.dispatch('STATUS', 'invisible')
966 if initial:
967 #ask our VCard
968 self.request_vcard(None)
970 #Get bookmarks from private namespace
971 self.get_bookmarks()
973 #Get annotations
974 self.get_annotations()
976 #Inform GUI we just signed in
977 self.dispatch('SIGNED_IN', ())
979 def test_gpg_passphrase(self, password):
980 '''Returns 'ok', 'bad_pass' or 'expired' '''
981 if not self.gpg:
982 return False
983 self.gpg.passphrase = password
984 keyID = gajim.config.get_per('accounts', self.name, 'keyid')
985 signed = self.gpg.sign('test', keyID)
986 self.gpg.password = None
987 if signed == 'KEYEXPIRED':
988 return 'expired'
989 elif signed == 'BAD_PASSPHRASE':
990 return 'bad_pass'
991 return 'ok'
993 def get_signed_presence(self, msg, callback = None):
994 if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'):
995 return self.get_signed_msg(msg, callback)
996 return ''
998 def get_signed_msg(self, msg, callback = None):
999 '''returns the signed message if possible
1000 or an empty string if gpg is not used
1001 or None if waiting for passphrase.
1002 callback is the function to call when user give the passphrase'''
1003 signed = ''
1004 keyID = gajim.config.get_per('accounts', self.name, 'keyid')
1005 if keyID and self.USE_GPG:
1006 use_gpg_agent = gajim.config.get('use_gpg_agent')
1007 if self.gpg.passphrase is None and not use_gpg_agent:
1008 # We didn't set a passphrase
1009 return None
1010 if self.gpg.passphrase is not None or use_gpg_agent:
1011 signed = self.gpg.sign(msg, keyID)
1012 if signed == 'BAD_PASSPHRASE':
1013 self.USE_GPG = False
1014 signed = ''
1015 self.dispatch('BAD_PASSPHRASE', ())
1016 return signed
1018 def connect_and_auth(self):
1019 self.on_connect_success = self._connect_success
1020 self.on_connect_failure = self._connect_failure
1021 self.connect()
1023 def connect_and_init(self, show, msg, sign_msg):
1024 self.continue_connect_info = [show, msg, sign_msg]
1025 self.on_connect_auth = self._init_roster
1026 self.connect_and_auth()
1028 def _init_roster(self, con):
1029 self.connection = con
1030 if not self.connection:
1031 return
1032 self.connection.set_send_timeout(self.keepalives, self.send_keepalive)
1033 self.connection.set_send_timeout2(self.pingalives, self.sendPing)
1034 self.connection.onreceive(None)
1035 iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '')
1036 id_ = self.connection.getAnID()
1037 iq.setID(id_)
1038 self.awaiting_answers[id_] = (PRIVACY_ARRIVED, )
1039 self.connection.send(iq)
1041 def send_custom_status(self, show, msg, jid):
1042 if not show in gajim.SHOW_LIST:
1043 return -1
1044 if not self.connection:
1045 return
1046 sshow = helpers.get_xmpp_show(show)
1047 if not msg:
1048 msg = ''
1049 if show == 'offline':
1050 p = common.xmpp.Presence(typ = 'unavailable', to = jid)
1051 p = self.add_sha(p, False)
1052 if msg:
1053 p.setStatus(msg)
1054 else:
1055 signed = self.get_signed_presence(msg)
1056 priority = unicode(gajim.get_priority(self.name, sshow))
1057 p = common.xmpp.Presence(typ = None, priority = priority, show = sshow,
1058 to = jid)
1059 p = self.add_sha(p)
1060 if msg:
1061 p.setStatus(msg)
1062 if signed:
1063 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
1064 self.connection.send(p)
1066 def change_status(self, show, msg, auto = False):
1067 if not show in gajim.SHOW_LIST:
1068 return -1
1069 sshow = helpers.get_xmpp_show(show)
1070 if not msg:
1071 msg = ''
1072 sign_msg = False
1073 if not auto and not show == 'offline':
1074 sign_msg = True
1075 if show != 'invisible':
1076 # We save it only when privacy list is accepted
1077 self.status = msg
1078 if show != 'offline' and self.connected < 1:
1079 # set old_show to requested 'show' in case we need to
1080 # recconect before we auth to server
1081 self.old_show = show
1082 self.on_purpose = False
1083 self.server_resource = gajim.config.get_per('accounts', self.name,
1084 'resource')
1085 # All valid resource substitution strings should be added to this hash.
1086 if self.server_resource:
1087 self.server_resource = Template(self.server_resource).\
1088 safe_substitute({
1089 'hostname': socket.gethostname()
1090 })
1091 if gajim.HAVE_GPG:
1092 self.USE_GPG = True
1093 self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
1094 self.connect_and_init(show, msg, sign_msg)
1096 elif show == 'offline':
1097 self.connected = 0
1098 if self.connection:
1099 self.terminate_sessions()
1101 self.on_purpose = True
1102 p = common.xmpp.Presence(typ = 'unavailable')
1103 p = self.add_sha(p, False)
1104 if msg:
1105 p.setStatus(msg)
1106 self.remove_all_transfers()
1107 self.time_to_reconnect = None
1109 self.connection.RegisterDisconnectHandler(self._on_disconnected)
1110 self.connection.send(p, now=True)
1111 self.connection.start_disconnect()
1112 #self.connection.start_disconnect(p, self._on_disconnected)
1113 else:
1114 self.time_to_reconnect = None
1115 self._on_disconnected()
1117 elif show != 'offline' and self.connected > 0:
1118 # dont'try to connect, when we are in state 'connecting'
1119 if self.connected == 1:
1120 return
1121 if show == 'invisible':
1122 signed = self.get_signed_presence(msg)
1123 self.send_invisible_presence(msg, signed)
1124 return
1125 was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
1126 self.connected = gajim.SHOW_LIST.index(show)
1127 if was_invisible and self.privacy_rules_supported:
1128 iq = self.build_privacy_rule('visible', 'allow')
1129 self.connection.send(iq)
1130 self.activate_privacy_rule('visible')
1131 priority = unicode(gajim.get_priority(self.name, sshow))
1132 p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
1133 p = self.add_sha(p)
1134 if msg:
1135 p.setStatus(msg)
1136 signed = self.get_signed_presence(msg)
1137 if signed:
1138 p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
1139 if self.connection:
1140 self.connection.send(p)
1141 self.priority = priority
1142 self.dispatch('STATUS', show)
1144 def _on_disconnected(self):
1145 ''' called when a disconnect request has completed successfully'''
1146 self.disconnect(on_purpose=True)
1147 self.dispatch('STATUS', 'offline')
1149 def get_status(self):
1150 return gajim.SHOW_LIST[self.connected]
1153 def send_motd(self, jid, subject = '', msg = '', xhtml = None):
1154 if not self.connection:
1155 return
1156 msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject,
1157 xhtml = xhtml)
1159 self.connection.send(msg_iq)
1161 def send_message(self, jid, msg, keyID, type_='chat', subject='',
1162 chatstate=None, msg_id=None, composing_xep=None, resource=None,
1163 user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
1164 original_message=None, delayed=None, callback=None, callback_args=[]):
1165 if not self.connection or self.connected < 2:
1166 return 1
1167 try:
1168 jid = helpers.parse_jid(jid)
1169 except helpers.InvalidFormat:
1170 self.dispatch('ERROR', (_('Invalid Jabber ID'),
1171 _('It is not possible to send a message to %s, this JID is not '
1172 'valid.') % jid))
1173 return
1175 if msg and not xhtml and gajim.config.get(
1176 'rst_formatting_outgoing_messages'):
1177 from common.rst_xhtml_generator import create_xhtml
1178 xhtml = create_xhtml(msg)
1179 if not msg and chatstate is None and form_node is None:
1180 return
1181 fjid = jid
1182 if resource:
1183 fjid += '/' + resource
1184 msgtxt = msg
1185 msgenc = ''
1187 if session:
1188 fjid = str(session.jid)
1190 if keyID and self.USE_GPG:
1191 xhtml = None
1192 if keyID == 'UNKNOWN':
1193 error = _('Neither the remote presence is signed, nor a key was assigned.')
1194 elif keyID.endswith('MISMATCH'):
1195 error = _('The contact\'s key (%s) does not match the key assigned in Gajim.' % keyID[:8])
1196 else:
1197 def encrypt_thread(msg, keyID, always_trust=False):
1198 # encrypt message. This function returns (msgenc, error)
1199 return self.gpg.encrypt(msg, [keyID], always_trust)
1200 def _on_encrypted(output):
1201 msgenc, error = output
1202 if error == 'NOT_TRUSTED':
1203 def _on_always_trust(answer):
1204 if answer:
1205 gajim.thread_interface(encrypt_thread, [msg, keyID,
1206 True], _on_encrypted, [])
1207 else:
1208 self._on_message_encrypted(output, type_, msg, msgtxt,
1209 original_message, fjid, resource, jid, xhtml,
1210 subject, chatstate, composing_xep, forward_from,
1211 delayed, session, form_node, user_nick, keyID,
1212 callback, callback_args)
1213 self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust)
1214 else:
1215 self._on_message_encrypted(output, type_, msg, msgtxt,
1216 original_message, fjid, resource, jid, xhtml, subject,
1217 chatstate, composing_xep, forward_from, delayed, session,
1218 form_node, user_nick, keyID, callback, callback_args)
1219 gajim.thread_interface(encrypt_thread, [msg, keyID, False],
1220 _on_encrypted, [])
1221 return
1223 self._on_message_encrypted(('', error), type_, msg, msgtxt,
1224 original_message, fjid, resource, jid, xhtml, subject, chatstate,
1225 composing_xep, forward_from, delayed, session, form_node, user_nick,
1226 keyID, callback, callback_args)
1228 self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
1229 resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
1230 forward_from, delayed, session, form_node, user_nick, callback,
1231 callback_args)
1233 def _on_message_encrypted(self, output, type_, msg, msgtxt, original_message,
1234 fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from,
1235 delayed, session, form_node, user_nick, keyID, callback, callback_args):
1236 msgenc, error = output
1238 if msgenc and not error:
1239 msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
1240 lang = os.getenv('LANG')
1241 if lang is not None and lang != 'en': # we're not english
1242 # one in locale and one en
1243 msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
1244 ' (' + msgtxt + ')'
1245 self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
1246 resource, jid, xhtml, subject, msgenc, keyID, chatstate,
1247 composing_xep, forward_from, delayed, session, form_node, user_nick,
1248 callback, callback_args)
1249 return
1250 # Encryption failed, do not send message
1251 tim = localtime()
1252 self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
1254 def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
1255 resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
1256 forward_from, delayed, session, form_node, user_nick, callback,
1257 callback_args):
1258 if type_ == 'chat':
1259 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
1260 xhtml=xhtml)
1261 else:
1262 if subject:
1263 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
1264 subject=subject, xhtml=xhtml)
1265 else:
1266 msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
1267 xhtml=xhtml)
1268 if msgenc:
1269 msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
1271 if form_node:
1272 msg_iq.addChild(node=form_node)
1274 # XEP-0172: user_nickname
1275 if user_nick:
1276 msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
1277 user_nick)
1279 # TODO: We might want to write a function so we don't need to
1280 # reproduce that ugly if somewhere else.
1281 if resource:
1282 contact = gajim.contacts.get_contact(self.name, jid, resource)
1283 else:
1284 contact = gajim.contacts.get_contact_with_highest_priority(self.name,
1285 jid)
1287 # chatstates - if peer supports xep85 or xep22, send chatstates
1288 # please note that the only valid tag inside a message containing a <body>
1289 # tag is the active event
1290 if chatstate is not None:
1291 # XXX: Once we have fallback to disco,
1292 # remove notexistant check
1293 if ((composing_xep == 'XEP-0085' or not composing_xep) \
1294 and composing_xep != 'asked_once') or \
1295 (gajim.capscache.is_supported(contact,
1296 common.xmpp.NS_CHATSTATES) and \
1297 not gajim.capscache.is_supported(contact,
1298 'notexistant')):
1299 # XEP-0085
1300 msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
1301 if composing_xep in ('XEP-0022', 'asked_once') or \
1302 not composing_xep:
1303 # XEP-0022
1304 chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT)
1305 if chatstate is 'composing' or msgtxt:
1306 chatstate_node.addChild(name='composing')
1308 if forward_from:
1309 addresses = msg_iq.addChild('addresses',
1310 namespace=common.xmpp.NS_ADDRESS)
1311 addresses.addChild('address', attrs = {'type': 'ofrom',
1312 'jid': forward_from})
1314 # XEP-0203
1315 if delayed:
1316 our_jid = gajim.get_jid_from_account(self.name) + '/' + \
1317 self.server_resource
1318 timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed))
1319 msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2,
1320 attrs={'from': our_jid, 'stamp': timestamp})
1322 # XEP-0184
1323 if msgtxt and gajim.config.get_per('accounts', self.name,
1324 'request_receipt') and gajim.capscache.is_supported(contact,
1325 common.xmpp.NS_RECEIPTS):
1326 msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
1328 if session:
1329 # XEP-0201
1330 session.last_send = time.time()
1331 msg_iq.setThread(session.thread_id)
1333 # XEP-0200
1334 if session.enable_encryption:
1335 msg_iq = session.encrypt_stanza(msg_iq)
1337 msg_id = self.connection.send(msg_iq)
1338 if not forward_from and session and session.is_loggable():
1339 ji = gajim.get_jid_without_resource(jid)
1340 if gajim.config.should_log(self.name, ji):
1341 log_msg = msg
1342 if original_message is not None:
1343 log_msg = original_message
1344 if subject:
1345 log_msg = _('Subject: %(subject)s\n%(message)s') % \
1346 {'subject': subject, 'message': msg}
1347 if log_msg:
1348 if type_ == 'chat':
1349 kind = 'chat_msg_sent'
1350 else:
1351 kind = 'single_msg_sent'
1352 try:
1353 gajim.logger.write(kind, jid, log_msg)
1354 except exceptions.PysqliteOperationalError, e:
1355 self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
1356 except exceptions.DatabaseMalformed:
1357 pritext = _('Database Error')
1358 sectext = _('The database file (%s) cannot be read. Try to '
1359 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
1360 ' or remove it (all history will be lost).') % \
1361 common.logger.LOG_DB_PATH
1362 self.dispatch('MSGSENT', (jid, msg, keyID))
1364 if callback:
1365 callback(msg_id, *callback_args)
1367 def send_contacts(self, contacts, jid):
1368 '''Send contacts with RosterX (Xep-0144)'''
1369 if not self.connection:
1370 return
1371 if len(contacts) == 1:
1372 msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(),
1373 contacts[0].get_shown_name())
1374 else:
1375 msg = _('Sent contacts:')
1376 for contact in contacts:
1377 msg += '\n "%s" (%s)' % (contact.get_full_jid(),
1378 contact.get_shown_name())
1379 msg_iq = common.xmpp.Message(to=jid, body=msg)
1380 x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX)
1381 for contact in contacts:
1382 x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid,
1383 'name': contact.get_shown_name()})
1384 self.connection.send(msg_iq)
1386 def send_stanza(self, stanza):
1387 ''' send a stanza untouched '''
1388 if not self.connection:
1389 return
1390 self.connection.send(stanza)
1392 def ack_subscribed(self, jid):
1393 if not self.connection:
1394 return
1395 log.debug('ack\'ing subscription complete for %s' % jid)
1396 p = common.xmpp.Presence(jid, 'subscribe')
1397 self.connection.send(p)
1399 def ack_unsubscribed(self, jid):
1400 if not self.connection:
1401 return
1402 log.debug('ack\'ing unsubscription complete for %s' % jid)
1403 p = common.xmpp.Presence(jid, 'unsubscribe')
1404 self.connection.send(p)
1406 def request_subscription(self, jid, msg = '', name = '', groups = [],
1407 auto_auth = False, user_nick = ''):
1408 if not self.connection:
1409 return
1410 log.debug('subscription request for %s' % jid)
1411 if auto_auth:
1412 self.jids_for_auto_auth.append(jid)
1413 # RFC 3921 section 8.2
1414 infos = {'jid': jid}
1415 if name:
1416 infos['name'] = name
1417 iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER)
1418 q = iq.getTag('query')
1419 item = q.addChild('item', attrs = infos)
1420 for g in groups:
1421 item.addChild('group').setData(g)
1422 self.connection.send(iq)
1424 p = common.xmpp.Presence(jid, 'subscribe')
1425 if user_nick:
1426 p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick)
1427 p = self.add_sha(p)
1428 if msg:
1429 p.setStatus(msg)
1430 self.connection.send(p)
1432 def send_authorization(self, jid):
1433 if not self.connection:
1434 return
1435 p = common.xmpp.Presence(jid, 'subscribed')
1436 p = self.add_sha(p)
1437 self.connection.send(p)
1439 def refuse_authorization(self, jid):
1440 if not self.connection:
1441 return
1442 p = common.xmpp.Presence(jid, 'unsubscribed')
1443 p = self.add_sha(p)
1444 self.connection.send(p)
1446 def unsubscribe(self, jid, remove_auth = True):
1447 if not self.connection:
1448 return
1449 if remove_auth:
1450 self.connection.getRoster().delItem(jid)
1451 jid_list = gajim.config.get_per('contacts')
1452 for j in jid_list:
1453 if j.startswith(jid):
1454 gajim.config.del_per('contacts', j)
1455 else:
1456 self.connection.getRoster().Unsubscribe(jid)
1457 self.update_contact(jid, '', [])
1459 def unsubscribe_agent(self, agent):
1460 if not self.connection:
1461 return
1462 iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
1463 iq.getTag('query').setTag('remove')
1464 id_ = self.connection.getAnID()
1465 iq.setID(id_)
1466 self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
1467 self.connection.send(iq)
1468 self.connection.getRoster().delItem(agent)
1470 def update_contact(self, jid, name, groups):
1471 '''update roster item on jabber server'''
1472 if self.connection:
1473 self.connection.getRoster().setItem(jid = jid, name = name,
1474 groups = groups)
1476 def send_new_account_infos(self, form, is_form):
1477 if is_form:
1478 # Get username and password and put them in new_account_info
1479 for field in form.iter_fields():
1480 if field.var == 'username':
1481 self.new_account_info['name'] = field.value
1482 if field.var == 'password':
1483 self.new_account_info['password'] = field.value
1484 else:
1485 # Get username and password and put them in new_account_info
1486 if 'username' in form:
1487 self.new_account_info['name'] = form['username']
1488 if 'password' in form:
1489 self.new_account_info['password'] = form['password']
1490 self.new_account_form = form
1491 self.new_account(self.name, self.new_account_info)
1493 def new_account(self, name, config, sync = False):
1494 # If a connection already exist we cannot create a new account
1495 if self.connection:
1496 return
1497 self._hostname = config['hostname']
1498 self.new_account_info = config
1499 self.name = name
1500 self.on_connect_success = self._on_new_account
1501 self.on_connect_failure = self._on_new_account
1502 self.connect(config)
1504 def _on_new_account(self, con = None, con_type = None):
1505 if not con_type:
1506 if len(self._connection_types) or len(self._hosts):
1507 # There are still other way to try to connect
1508 return
1509 self.dispatch('NEW_ACC_NOT_CONNECTED',
1510 (_('Could not connect to "%s"') % self._hostname))
1511 return
1512 self.on_connect_failure = None
1513 self.connection = con
1514 common.xmpp.features_nb.getRegInfo(con, self._hostname)
1516 def account_changed(self, new_name):
1517 self.name = new_name
1519 def request_last_status_time(self, jid, resource, groupchat_jid=None):
1520 '''groupchat_jid is used when we want to send a request to a real jid
1521 and act as if the answer comes from the groupchat_jid'''
1522 if not self.connection:
1523 return
1524 to_whom_jid = jid
1525 if resource:
1526 to_whom_jid += '/' + resource
1527 iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\
1528 common.xmpp.NS_LAST)
1529 id_ = self.connection.getAnID()
1530 iq.setID(id_)
1531 if groupchat_jid:
1532 self.groupchat_jids[id_] = groupchat_jid
1533 self.last_ids.append(id_)
1534 self.connection.send(iq)
1536 def request_os_info(self, jid, resource, groupchat_jid=None):
1537 '''groupchat_jid is used when we want to send a request to a real jid
1538 and act as if the answer comes from the groupchat_jid'''
1539 if not self.connection:
1540 return
1541 # If we are invisible, do not request
1542 if self.connected == gajim.SHOW_LIST.index('invisible'):
1543 self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status')))
1544 return
1545 to_whom_jid = jid
1546 if resource:
1547 to_whom_jid += '/' + resource
1548 iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\
1549 common.xmpp.NS_VERSION)
1550 id_ = self.connection.getAnID()
1551 iq.setID(id_)
1552 if groupchat_jid:
1553 self.groupchat_jids[id_] = groupchat_jid
1554 self.version_ids.append(id_)
1555 self.connection.send(iq)
1557 def request_entity_time(self, jid, resource, groupchat_jid=None):
1558 '''groupchat_jid is used when we want to send a request to a real jid
1559 and act as if the answer comes from the groupchat_jid'''
1560 if not self.connection:
1561 return
1562 # If we are invisible, do not request
1563 if self.connected == gajim.SHOW_LIST.index('invisible'):
1564 self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status')))
1565 return
1566 to_whom_jid = jid
1567 if resource:
1568 to_whom_jid += '/' + resource
1569 iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\
1570 common.xmpp.NS_TIME_REVISED)
1571 id_ = self.connection.getAnID()
1572 iq.setID(id_)
1573 if groupchat_jid:
1574 self.groupchat_jids[id_] = groupchat_jid
1575 self.entity_time_ids.append(id_)
1576 self.connection.send(iq)
1578 def get_settings(self):
1579 ''' Get Gajim settings as described in XEP 0049 '''
1580 if not self.connection:
1581 return
1582 iq = common.xmpp.Iq(typ='get')
1583 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
1584 iq2.addChild(name='gajim', namespace='gajim:prefs')
1585 self.connection.send(iq)
1587 def get_bookmarks(self):
1588 '''Get Bookmarks from storage as described in XEP 0048'''
1589 self.bookmarks = [] #avoid multiple bookmarks when re-connecting
1590 if not self.connection:
1591 return
1592 iq = common.xmpp.Iq(typ='get')
1593 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
1594 iq2.addChild(name='storage', namespace='storage:bookmarks')
1595 self.connection.send(iq)
1597 def store_bookmarks(self):
1598 ''' Send bookmarks to the storage namespace '''
1599 if not self.connection:
1600 return
1601 iq = common.xmpp.Iq(typ='set')
1602 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
1603 iq3 = iq2.addChild(name='storage', namespace='storage:bookmarks')
1604 for bm in self.bookmarks:
1605 iq4 = iq3.addChild(name = "conference")
1606 iq4.setAttr('jid', bm['jid'])
1607 iq4.setAttr('autojoin', bm['autojoin'])
1608 iq4.setAttr('minimize', bm['minimize'])
1609 iq4.setAttr('name', bm['name'])
1610 # Only add optional elements if not empty
1611 # Note: need to handle both None and '' as empty
1612 # thus shouldn't use "is not None"
1613 if bm.get('nick', None):
1614 iq4.setTagData('nick', bm['nick'])
1615 if bm.get('password', None):
1616 iq4.setTagData('password', bm['password'])
1617 if bm.get('print_status', None):
1618 iq4.setTagData('print_status', bm['print_status'])
1619 self.connection.send(iq)
1621 def get_annotations(self):
1622 '''Get Annonations from storage as described in XEP 0048, and XEP 0145'''
1623 self.annotations = {}
1624 if not self.connection:
1625 return
1626 iq = common.xmpp.Iq(typ='get')
1627 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
1628 iq2.addChild(name='storage', namespace='storage:rosternotes')
1629 self.connection.send(iq)
1631 def store_annotations(self):
1632 '''Set Annonations in private storage as described in XEP 0048, and XEP 0145'''
1633 if not self.connection:
1634 return
1635 iq = common.xmpp.Iq(typ='set')
1636 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
1637 iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes')
1638 for jid in self.annotations.keys():
1639 if self.annotations[jid]:
1640 iq4 = iq3.addChild(name = "note")
1641 iq4.setAttr('jid', jid)
1642 iq4.setData(self.annotations[jid])
1643 self.connection.send(iq)
1646 def get_metacontacts(self):
1647 '''Get metacontacts list from storage as described in XEP 0049'''
1648 if not self.connection:
1649 return
1650 iq = common.xmpp.Iq(typ='get')
1651 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
1652 iq2.addChild(name='storage', namespace='storage:metacontacts')
1653 id_ = self.connection.getAnID()
1654 iq.setID(id_)
1655 self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, )
1656 self.connection.send(iq)
1658 def store_metacontacts(self, tags_list):
1659 ''' Send meta contacts to the storage namespace '''
1660 if not self.connection:
1661 return
1662 iq = common.xmpp.Iq(typ='set')
1663 iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
1664 iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts')
1665 for tag in tags_list:
1666 for data in tags_list[tag]:
1667 jid = data['jid']
1668 dict_ = {'jid': jid, 'tag': tag}
1669 if 'order' in data:
1670 dict_['order'] = data['order']
1671 iq3.addChild(name = 'meta', attrs = dict_)
1672 self.connection.send(iq)
1674 def send_agent_status(self, agent, ptype):
1675 if not self.connection:
1676 return
1677 show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
1678 p = common.xmpp.Presence(to = agent, typ = ptype, show = show)
1679 p = self.add_sha(p, ptype != 'unavailable')
1680 self.connection.send(p)
1682 def check_unique_room_id_support(self, server, instance):
1683 if not self.connection:
1684 return
1685 iq = common.xmpp.Iq(typ = 'get', to = server)
1686 iq.setAttr('id', 'unique1')
1687 iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE)
1688 def _on_response(resp):
1689 if not common.xmpp.isResultNode(resp):
1690 self.dispatch('UNIQUE_ROOM_ID_UNSUPPORTED', (server, instance))
1691 return
1692 self.dispatch('UNIQUE_ROOM_ID_SUPPORTED', (server, instance,
1693 resp.getTag('unique').getData()))
1694 self.connection.SendAndCallForResponse(iq, _on_response)
1696 def join_gc(self, nick, room_jid, password, change_nick=False):
1697 # FIXME: This room JID needs to be normalized; see #1364
1698 if not self.connection:
1699 return
1700 show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
1701 if show == 'invisible':
1702 # Never join a room when invisible
1703 return
1704 p = common.xmpp.Presence(to = '%s/%s' % (room_jid, nick),
1705 show = show, status = self.status)
1706 if gajim.config.get('send_sha_in_gc_presence'):
1707 p = self.add_sha(p)
1708 self.add_lang(p)
1709 if not change_nick:
1710 t = p.setTag(common.xmpp.NS_MUC + ' x')
1711 if password:
1712 t.setTagData('password', password)
1713 self.connection.send(p)
1715 # last date/time in history to avoid duplicate
1716 if room_jid not in self.last_history_time:
1717 # Not in memory, get it from DB
1718 last_log = None
1719 # Do not check if we are not logging for this room
1720 if gajim.config.should_log(self.name, room_jid):
1721 # Check time first in the FAST table
1722 last_log = gajim.logger.get_room_last_message_time(room_jid)
1723 if last_log is None:
1724 # Not in special table, get it from messages DB
1725 last_log = gajim.logger.get_last_date_that_has_logs(room_jid,
1726 is_room = True)
1727 # Create self.last_history_time[room_jid] even if not logging,
1728 # could be used in connection_handlers
1729 if last_log is None:
1730 last_log = 0
1731 self.last_history_time[room_jid]= last_log
1733 def send_gc_message(self, jid, msg, xhtml = None):
1734 if not self.connection:
1735 return
1736 if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
1737 from common.rst_xhtml_generator import create_xhtml
1738 xhtml = create_xhtml(msg)
1739 msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml)
1740 self.connection.send(msg_iq)
1741 self.dispatch('MSGSENT', (jid, msg))
1743 def send_gc_subject(self, jid, subject):
1744 if not self.connection:
1745 return
1746 msg_iq = common.xmpp.Message(jid,typ = 'groupchat', subject = subject)
1747 self.connection.send(msg_iq)
1749 def request_gc_config(self, room_jid):
1750 if not self.connection:
1751 return
1752 iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER,
1753 to = room_jid)
1754 self.add_lang(iq)
1755 self.connection.send(iq)
1757 def destroy_gc_room(self, room_jid, reason = '', jid = ''):
1758 if not self.connection:
1759 return
1760 iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER,
1761 to = room_jid)
1762 destroy = iq.getTag('query').setTag('destroy')
1763 if reason:
1764 destroy.setTagData('reason', reason)
1765 if jid:
1766 destroy.setAttr('jid', jid)
1767 self.connection.send(iq)
1769 def send_gc_status(self, nick, jid, show, status):
1770 if not gajim.account_is_connected(self.name):
1771 return
1772 if show == 'invisible':
1773 show = 'offline'
1774 ptype = None
1775 if show == 'offline':
1776 ptype = 'unavailable'
1777 xmpp_show = helpers.get_xmpp_show(show)
1778 p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype,
1779 show = xmpp_show, status = status)
1780 if gajim.config.get('send_sha_in_gc_presence') and show != 'offline':
1781 p = self.add_sha(p, ptype != 'unavailable')
1782 self.add_lang(p)
1783 # send instantly so when we go offline, status is sent to gc before we
1784 # disconnect from jabber server
1785 self.connection.send(p)
1787 def gc_got_disconnected(self, room_jid):
1788 ''' A groupchat got disconnected. This can be or purpose or not.
1789 Save the time we quit to avoid duplicate logs AND be faster than get that
1790 date from DB. Save it in mem AND in a small table (with fast access)
1791 '''
1792 log_time = time_time()
1793 self.last_history_time[room_jid] = log_time
1794 gajim.logger.set_room_last_message_time(room_jid, log_time)
1796 def gc_set_role(self, room_jid, nick, role, reason = ''):
1797 '''role is for all the life of the room so it's based on nick'''
1798 if not self.connection:
1799 return
1800 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
1801 common.xmpp.NS_MUC_ADMIN)
1802 item = iq.getTag('query').setTag('item')
1803 item.setAttr('nick', nick)
1804 item.setAttr('role', role)
1805 if reason:
1806 item.addChild(name = 'reason', payload = reason)
1807 self.connection.send(iq)
1809 def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
1810 '''affiliation is for all the life of the room so it's based on jid'''
1811 if not self.connection:
1812 return
1813 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
1814 common.xmpp.NS_MUC_ADMIN)
1815 item = iq.getTag('query').setTag('item')
1816 item.setAttr('jid', jid)
1817 item.setAttr('affiliation', affiliation)
1818 if reason:
1819 item.addChild(name = 'reason', payload = reason)
1820 self.connection.send(iq)
1822 def send_gc_affiliation_list(self, room_jid, users_dict):
1823 if not self.connection:
1824 return
1825 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \
1826 common.xmpp.NS_MUC_ADMIN)
1827 item = iq.getTag('query')
1828 for jid in users_dict:
1829 item_tag = item.addChild('item', {'jid': jid,
1830 'affiliation': users_dict[jid]['affiliation']})
1831 if 'reason' in users_dict[jid] and users_dict[jid]['reason']:
1832 item_tag.setTagData('reason', users_dict[jid]['reason'])
1833 self.connection.send(iq)
1835 def get_affiliation_list(self, room_jid, affiliation):
1836 if not self.connection:
1837 return
1838 iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \
1839 common.xmpp.NS_MUC_ADMIN)
1840 item = iq.getTag('query').setTag('item')
1841 item.setAttr('affiliation', affiliation)
1842 self.connection.send(iq)
1844 def send_gc_config(self, room_jid, form):
1845 iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
1846 common.xmpp.NS_MUC_OWNER)
1847 query = iq.getTag('query')
1848 form.setAttr('type', 'submit')
1849 query.addChild(node = form)
1850 self.connection.send(iq)
1852 def gpg_passphrase(self, passphrase):
1853 if self.gpg:
1854 use_gpg_agent = gajim.config.get('use_gpg_agent')
1855 if use_gpg_agent:
1856 self.gpg.passphrase = None
1857 else:
1858 self.gpg.passphrase = passphrase
1860 def ask_gpg_keys(self):
1861 if self.gpg:
1862 keys = self.gpg.get_keys()
1863 return keys
1864 return None
1866 def ask_gpg_secrete_keys(self):
1867 if self.gpg:
1868 keys = self.gpg.get_secret_keys()
1869 return keys
1870 return None
1872 def change_password(self, password):
1873 if not self.connection:
1874 return
1875 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
1876 username = gajim.config.get_per('accounts', self.name, 'name')
1877 iq = common.xmpp.Iq(typ = 'set', to = hostname)
1878 q = iq.setTag(common.xmpp.NS_REGISTER + ' query')
1879 q.setTagData('username',username)
1880 q.setTagData('password',password)
1881 self.connection.send(iq)
1883 def get_password(self, callback):
1884 if self.password:
1885 callback(self.password)
1886 return
1887 self.pasword_callback = callback
1888 self.dispatch('PASSWORD_REQUIRED', None)
1890 def set_password(self, password):
1891 self.password = password
1892 if self.pasword_callback:
1893 self.pasword_callback(password)
1894 self.pasword_callback = None
1896 def unregister_account(self, on_remove_success):
1897 # no need to write this as a class method and keep the value of
1898 # on_remove_success as a class property as pass it as an argument
1899 def _on_unregister_account_connect(con):
1900 self.on_connect_auth = None
1901 if gajim.account_is_connected(self.name):
1902 hostname = gajim.config.get_per('accounts', self.name, 'hostname')
1903 iq = common.xmpp.Iq(typ = 'set', to = hostname)
1904 iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove')
1905 con.send(iq)
1906 on_remove_success(True)
1907 return
1908 on_remove_success(False)
1909 if self.connected == 0:
1910 self.on_connect_auth = _on_unregister_account_connect
1911 self.connect_and_auth()
1912 else:
1913 _on_unregister_account_connect(self.connection)
1915 def send_invite(self, room, to, reason='', continue_tag=False):
1916 '''sends invitation'''
1917 message=common.xmpp.Message(to = room)
1918 c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER)
1919 c = c.addChild(name = 'invite', attrs={'to' : to})
1920 if continue_tag:
1921 c.addChild(name = 'continue')
1922 if reason != '':
1923 c.setTagData('reason', reason)
1924 self.connection.send(message)
1926 def check_pingalive(self):
1927 if self.awaiting_xmpp_ping_id:
1928 # We haven't got the pong in time, disco and reconnect
1929 self._disconnectedReconnCB()
1931 def _reconnect_alarm(self):
1932 if self.time_to_reconnect:
1933 if self.connected < 2:
1934 self._reconnect()
1935 else:
1936 self.time_to_reconnect = None
1938 def request_search_fields(self, jid):
1939 iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \
1940 common.xmpp.NS_SEARCH)
1941 self.connection.send(iq)
1943 def send_search_form(self, jid, form, is_form):
1944 iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \
1945 common.xmpp.NS_SEARCH)
1946 item = iq.getTag('query')
1947 if is_form:
1948 item.addChild(node = form)
1949 else:
1950 for i in form.keys():
1951 item.setTagData(i,form[i])
1952 def _on_response(resp):
1953 jid = jid = helpers.get_jid_from_iq(resp)
1954 tag = resp.getTag('query', namespace = common.xmpp.NS_SEARCH)
1955 if not tag:
1956 self.dispatch('SEARCH_RESULT', (jid, None, False))
1957 return
1958 df = tag.getTag('x', namespace = common.xmpp.NS_DATA)
1959 if df:
1960 self.dispatch('SEARCH_RESULT', (jid, df, True))
1961 return
1962 df = []
1963 for item in tag.getTags('item'):
1964 # We also show attributes. jid is there
1965 f = item.attrs
1966 for i in item.getPayload():
1967 f[i.getName()] = i.getData()
1968 df.append(f)
1969 self.dispatch('SEARCH_RESULT', (jid, df, False))
1971 self.connection.SendAndCallForResponse(iq, _on_response)
1973 def load_roster_from_db(self):
1974 roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name))
1975 self.dispatch('ROSTER', roster)
1978 # END Connection
1980 # vim: se ts=3:
