connection.py 33.5 KB
Newer Older
1
##	common/connection.py
2
##
3
## Contributors for this file:
4
5
##	- Yann Le Boulanger <asterix@lagaule.org>
##	- Nikos Kouremenos <nkour@jabber.org>
6
##	- Dimitur Kirov <dkirov@gmail.com>
7
##	- Travis Shirk <travis@pobox.com>
8
##
9
10
11
12
13
14
15
16
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
##                         Vincent Hanquez <tab@snarc.org>
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
##                    Vincent Hanquez <tab@snarc.org>
##                    Nikos Kouremenos <nkour@jabber.org>
##                    Dimitur Kirov <dkirov@gmail.com>
##                    Travis Shirk <travis@pobox.com>
##                    Norman Rasmussen <norman@rasmussen.co.za>
17
18
19
20
21
22
23
24
25
26
27
##
## This program 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 2 only.
##
## This program 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.
##

28

29
30
31
import sys
import os
import time
32
import traceback
33
34
import random
random.seed()
35

36
import signal
Yann Leboulanger's avatar
Yann Leboulanger committed
37
38
if os.name != 'nt':
	signal.signal(signal.SIGPIPE, signal.SIG_DFL)
dkirov's avatar
dkirov committed
39

40
import common.xmpp
Yann Leboulanger's avatar
Yann Leboulanger committed
41
from common import helpers
Yann Leboulanger's avatar
Yann Leboulanger committed
42
43
from common import gajim
from common import GnuPG
44
45

from connection_handlers import *
46
USE_GPG = GnuPG.USE_GPG
47
48
49
50

from common import i18n
_ = i18n._

51
class Connection(ConnectionHandlers):
nkour's avatar
nkour committed
52
	'''Connection class'''
53
	def __init__(self, name):
54
		ConnectionHandlers.__init__(self)
55
56
		self.name = name
		self.connected = 0 # offline
57
		self.connection = None # xmpppy ClientCommon instance
58
		# this property is used to prevent double connections
59
		self.last_connection = None # last ClientCommon instance
60
		self.gpg = None
61
		self.status = ''
62
		self.old_show = ''
dkirov's avatar
dkirov committed
63
		# increase/decrease default timeout for server responses
64
		self.try_connecting_for_foo_secs = 45
65
		# holds the actual hostname to which we are connected
66
		self.connected_hostname = None
67
		self.time_to_reconnect = None
Yann Leboulanger's avatar
Yann Leboulanger committed
68
		self.new_account_info = None
69
		self.bookmarks = []
70
		self.on_purpose = False
71
		self.last_io = gajim.idlequeue.current_time()
72
		self.last_sent = []
73
		self.last_history_line = {}
74
		self.password = gajim.config.get_per('accounts', name, 'password')
dkirov's avatar
dkirov committed
75
		self.server_resource = gajim.config.get_per('accounts', name, 'resource')
dkirov's avatar
dkirov committed
76
77
78
79
		if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
			self.keepalives = gajim.config.get_per('accounts', self.name,'keep_alive_every_foo_secs')
		else:
			self.keepalives = 0
Yann Leboulanger's avatar
typo    
Yann Leboulanger committed
80
		self.privacy_rules_supported = False
81
		# Do we continue connection when we get roster (send presence,get vcard...)
Yann Leboulanger's avatar
typo    
Yann Leboulanger committed
82
		self.continue_connect_info = None
83
84
85
86
87
		if USE_GPG:
			self.gpg = GnuPG.GnuPG()
			gajim.config.set('usegpg', True)
		else:
			gajim.config.set('usegpg', False)
88
		
dkirov's avatar
dkirov committed
89
		self.on_connect_success = None
90
		self.retrycount = 0
91
		self.jids_for_auto_auth = [] # list of jid to auto-authorize
92
		
93
	# END __init__
94
	def put_event(self, ev):
dkirov's avatar
dkirov committed
95
96
		if gajim.handlers.has_key(ev[0]):
			gajim.handlers[ev[0]](self.name, ev[1])
97

98
	def dispatch(self, event, data):
99
		'''always passes account name as first param'''
dkirov's avatar
dkirov committed
100
		self.put_event((event, data))
101

102
103

	def _reconnect(self):
104
105
		# Do not try to reco while we are already trying
		self.time_to_reconnect = None
106
		gajim.log.debug('reconnect')
107
		self.retrycount += 1
108
		signed = self.get_signed_msg(self.status)
dkirov's avatar
dkirov committed
109
		self.on_connect_auth = self._init_roster
110
		self.connect_and_init(self.old_show, self.status, signed)
111

112
113
114
115
		if self.connected < 2: #connection failed
			if self.retrycount > 10:
				self.connected = 0
				self.dispatch('STATUS', 'offline')
116
				self.dispatch('ERROR',
117
118
119
120
121
				(_('Connection with account "%s" has been lost') % self.name,
				_('To continue sending and receiving messages, you will need to reconnect.')))
				self.retrycount = 0
				return
			if self.retrycount > 5:
dkirov's avatar
dkirov committed
122
				self.time_to_reconnect = 20
123
			else:
dkirov's avatar
dkirov committed
124
125
				self.time_to_reconnect = 10
			gajim.idlequeue.set_alarm(self._reconnect_alarm, self.time_to_reconnect)
126
		else:
dkirov's avatar
dkirov committed
127
			# reconnect succeeded
128
129
			self.time_to_reconnect = None
			self.retrycount = 0
130

131
	def _disconnectedReconnCB(self):
nkour's avatar
nkour committed
132
		'''Called when we are disconnected'''
133
134
135
136
137
138
		gajim.log.debug('disconnectedReconnCB')
		if not self.connection:
			return
		self.old_show = STATUS_LIST[self.connected]
		self.connected = 0
		self.dispatch('STATUS', 'offline')
139
		gajim.proxy65_manager.disconnect(self.connection)
140
141
142
143
144
		self.connection = None
		if not self.on_purpose:
			if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
				self.connected = 1
				self.dispatch('STATUS', 'connecting')
dkirov's avatar
dkirov committed
145
146
				self.time_to_reconnect = 10
				gajim.idlequeue.set_alarm(self._reconnect_alarm, 10)
147
			else:
148
				self.dispatch('ERROR',
149
150
151
152
				(_('Connection with account "%s" has been lost') % self.name,
				_('To continue sending and receiving messages, you will need to reconnect.')))
		self.on_purpose = False
	# END disconenctedReconnCB
153
	
Yann Leboulanger's avatar
Yann Leboulanger committed
154
	def _event_dispatcher(self, realm, event, data):
155
		if realm == common.xmpp.NS_REGISTER:
dkirov's avatar
dkirov committed
156
			if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:
157
				# data is (agent, DataFrom, is_form)
158
159
160
161
162
				if not data[1]: # wrong answer
					self.dispatch('ERROR', (_('Invalid answer'),
						_('Transport %s answered wrongly to register request.') % \
						data[0]))
					return
163
164
				if self.new_account_info and\
				self.new_account_info['hostname'] == data[0]:
Yann Leboulanger's avatar
Yann Leboulanger committed
165
166
167
168
					#it's a new account
					req = data[1].asDict()
					req['username'] = self.new_account_info['name']
					req['password'] = self.new_account_info['password']
dkirov's avatar
dkirov committed
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
					def _on_register_result(result):
						if not result:
							self.dispatch('ACC_NOT_OK', (self.connection.lastErr))
							return
						self.connected = 0
						self.password = self.new_account_info['password']
						if USE_GPG:
							self.gpg = GnuPG.GnuPG()
							gajim.config.set('usegpg', True)
						else:
							gajim.config.set('usegpg', False)
						gajim.connections[self.name] = self
						self.dispatch('ACC_OK', (self.new_account_info))
						self.new_account_info = None
						self.connection = None
184
					common.xmpp.features_nb.register(self.connection, data[0],
dkirov's avatar
dkirov committed
185
						req, _on_register_result)
186
					return
187
188
189
190
191
192
				is_form = data[2]
				if is_form:
					conf = self.parse_data_form(data[1])
				else:
					conf = data[1].asDict()
				self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form))
193
194
		elif realm == '':
			if event == common.xmpp.transports.DATA_RECEIVED:
195
				self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
196
			elif event == common.xmpp.transports.DATA_SENT:
197
				self.dispatch('STANZA_SENT', unicode(data))
198

199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
	def select_next_host(self, hosts):
		hosts_best_prio = []
		best_prio = 65535
		sum_weight = 0
		for h in hosts:
			if h['prio'] < best_prio:
				hosts_best_prio = [h]
				best_prio = h['prio']
				sum_weight = h['weight']
			elif h['prio'] == best_prio:
				hosts_best_prio.append(h)
				sum_weight += h['weight']
		if len(hosts_best_prio) == 1:
			return hosts_best_prio[0]
		r = random.randint(0, sum_weight)
		min_w = sum_weight
		# We return the one for which has the minimum weight and weight >= r
		for h in hosts_best_prio:
			if h['weight'] >= r:
				if h['weight'] <= min_w:
					min_w = h['weight']
		return h

222
	def connect(self, data = None):
dkirov's avatar
dkirov committed
223
		''' Start a connection to the Jabber server.
224
225
226
		Returns connection, and connection type ('tls', 'ssl', 'tcp', '')
		data MUST contain name, hostname, resource, usessl, proxy,
		use_custom_host, custom_host (if use_custom_host), custom_port (if
nkour's avatar
nkour committed
227
		use_custom_host), '''
Yann Leboulanger's avatar
Yann Leboulanger committed
228
		if self.connection:
229
			return self.connection, ''
Yann Leboulanger's avatar
Yann Leboulanger committed
230

231
232
233
234
235
		if data:
			name = data['name']
			hostname = data['hostname']
			resource = data['resource']
			usessl = data['usessl']
dkirov's avatar
dkirov committed
236
			self.try_connecting_for_foo_secs = 45
237
238
239
240
241
242
243
244
245
246
247
			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:
			name = gajim.config.get_per('accounts', self.name, 'name')
			hostname = gajim.config.get_per('accounts', self.name, 'hostname')
			resource = gajim.config.get_per('accounts', self.name, 'resource')
			usessl = gajim.config.get_per('accounts', self.name, 'usessl')
dkirov's avatar
dkirov committed
248
			self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
249
250
251
252
253
254
255
				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')
256

257
		#create connection if it doesn't already exist
258
		self.connected = 1
Yann Leboulanger's avatar
Yann Leboulanger committed
259
		if p and p in gajim.config.get_per('proxies'):
260
261
262
263
			proxy = {'host': gajim.config.get_per('proxies', p, 'host')}
			proxy['port'] = gajim.config.get_per('proxies', p, 'port')
			proxy['user'] = gajim.config.get_per('proxies', p, 'user')
			proxy['password'] = gajim.config.get_per('proxies', p, 'pass')
264
265
		else:
			proxy = None
266

Yann Leboulanger's avatar
Yann Leboulanger committed
267
268
		h = hostname
		p = 5222
nkour's avatar
nkour committed
269
270
		# autodetect [for SSL in 5223/443 and for TLS if broadcasted]
		secur = None
Yann Leboulanger's avatar
Yann Leboulanger committed
271
272
		if usessl:
			p = 5223
273
			secur = 1 # 1 means force SSL no matter what the port will be
274
			use_srv = False # wants ssl? disable srv lookup
275
276
277
		if use_custom:
			h = custom_h
			p = custom_p
278
			use_srv = False
279

280
		hosts = []
281
		# SRV resolver
dkirov's avatar
dkirov committed
282
283
284
285
286
287
288
289
290
		self._proxy = proxy
		self._secure = secur
		self._hosts = [ {'host': h, 'port': 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.' + h.encode('utf-8'), self._on_resolve)
		else:
			self._on_resolve('', [])
291

dkirov's avatar
dkirov committed
292
293
	def _on_resolve(self, host, result_array):
		# SRV query returned at least one valid result, we put it in hosts dict
294
		if len(result_array) != 0:
dkirov's avatar
dkirov committed
295
296
			self._hosts = [i for i in result_array]
		self.connect_to_next_host()
297

dkirov's avatar
dkirov committed
298
299
	def connect_to_next_host(self):
		if len(self._hosts):
300
301
302
			if self.last_connection:
				self.last_connection.socket.disconnect()
				self.last_connection = None
303
				self.connection = None
Yann Leboulanger's avatar
Yann Leboulanger committed
304
			if gajim.verbose:
305
306
				con = common.xmpp.NonBlockingClient(self._hostname, caller = self,
					on_connect = self.on_connect_success,
dkirov's avatar
dkirov committed
307
					on_connect_failure = self.connect_to_next_host)
Yann Leboulanger's avatar
Yann Leboulanger committed
308
			else:
309
310
				con = common.xmpp.NonBlockingClient(self._hostname, debug = [], caller = self,
					on_connect = self.on_connect_success,
dkirov's avatar
dkirov committed
311
					on_connect_failure = self.connect_to_next_host)
312
			self.last_connection = con
dkirov's avatar
dkirov committed
313
314
315
316
317
318
			# increase default timeout for server responses
			common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
			con.set_idlequeue(gajim.idlequeue)
			host = self.select_next_host(self._hosts)
			self._current_host = host
			self._hosts.remove(host)
319
			con.connect((host['host'], host['port']), proxy = self._proxy,
dkirov's avatar
dkirov committed
320
321
322
323
324
325
				secure = self._secure)
			return
		else:
			self._connect_failure(None)

	def _connect_failure(self, con_type):
326
		if not con_type:
dkirov's avatar
dkirov committed
327
328
			# we are not retrying, and not conecting
			if not self.retrycount and self.connected != 0:
329
330
				self.connected = 0
				self.dispatch('STATUS', 'offline')
dkirov's avatar
dkirov committed
331
				self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname,
332
					_('Check your connection or try again later.')))
333

dkirov's avatar
dkirov committed
334
335
	def _connect_success(self, con, con_type):
		if not self.connected: # We went offline during connecting process
336
			return None, ''
dkirov's avatar
dkirov committed
337
		self.hosts = []
338
		if not con_type:
dkirov's avatar
dkirov committed
339
340
341
342
343
344
345
346
347
348
349
350
			gajim.log.debug('Could not connect to %s:%s' % (self._current_host['host'],
				self._current_host['port']))
		self.connected_hostname = self._current_host['host']
		con.RegisterDisconnectHandler(self._disconnectedReconnCB)
		gajim.log.debug(_('Connected to server %s:%s with %s') % (self._current_host['host'],
			self._current_host['port'], con_type))
		# Ask meta_contacts before roster
		self.get_meta_contacts()
		self._register_handlers(con, con_type)
		return True

	def _register_handlers(self, con, con_type):
351
		self.peerhost = con.get_peerhost()
352
353
		# notify the gui about con_type
		self.dispatch('CON_TYPE', con_type)
354
		ConnectionHandlers._register_handlers(self, con, con_type)
355
356
357
		name = gajim.config.get_per('accounts', self.name, 'name')
		hostname = gajim.config.get_per('accounts', self.name, 'hostname')
		resource = gajim.config.get_per('accounts', self.name, 'resource')
dkirov's avatar
dkirov committed
358
359
		self.connection = con
		con.auth(name, self.password, resource, 1, self.__on_auth)
360

dkirov's avatar
dkirov committed
361
362
	def __on_auth(self, con, auth):
		if not con:
363
			self.connected = 0
364
			self.dispatch('STATUS', 'offline')
dkirov's avatar
dkirov committed
365
			self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname,
366
				_('Check your connection or try again later')))
dkirov's avatar
dkirov committed
367
368
369
370
			if self.on_connect_auth:
				self.on_connect_auth(None)
				self.on_connect_auth = None
				return
371
		if not self.connected: # We went offline during connecting process
dkirov's avatar
dkirov committed
372
373
374
375
			if self.on_connect_auth:
				self.on_connect_auth(None)
				self.on_connect_auth = None
				return
376
377
		if hasattr(con, 'Resource'):
			self.server_resource = con.Resource
378
		if auth:
379
			self.last_io = gajim.idlequeue.current_time()
380
			self.connected = 2
dkirov's avatar
dkirov committed
381
382
383
			if self.on_connect_auth:
				self.on_connect_auth(con)
				self.on_connect_auth = None
384
		else:
385
386
387
			# Forget password if needed
			if not gajim.config.get_per('accounts', self.name, 'savepass'):
				self.password = None
dkirov's avatar
dkirov committed
388
			gajim.log.debug("Couldn't authenticate to %s" % self._hostname)
389
			self.connected = 0
390
			self.dispatch('STATUS', 'offline')
dkirov's avatar
dkirov committed
391
			self.dispatch('ERROR', (_('Authentication failed with "%s"') % self._hostname,
nkour's avatar
nkour committed
392
				_('Please check your login and password for correctness.')))
dkirov's avatar
dkirov committed
393
394
395
			if self.on_connect_auth:
				self.on_connect_auth(None)
				self.on_connect_auth = None
396
	# END connect
397
398
399
400
401

	def quit(self, kill_core):
		if kill_core:
			if self.connected > 1:
				self.connected = 0
Yann Leboulanger's avatar
Yann Leboulanger committed
402
				self.connection.disconnect()
dkirov's avatar
dkirov committed
403
				self.time_to_reconnect = None
404
405
			return

406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
	def build_privacy_rule(self, name, action):
		'''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})
		i = l.setTag('item', {'action': action, 'order': '1'})
		i.setTag('presence-out')
		return iq

	def activate_privacy_rule(self, name):
		'''activate a privacy rule'''
		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):
		# try to set the privacy rule
		iq = self.build_privacy_rule('invisible', 'deny')
		self.connection.SendAndCallForResponse(iq, self._continue_invisible,
			{'msg': msg, 'signed': signed, 'initial': initial})
425

426
427
428
429
430
431
432
433
434
435
436
437
	def _continue_invisible(self, con, iq_obj, msg, signed, initial):
		ptype = ''
		show = ''
		# FIXME: JEP 126 need some modifications (see http://lists.jabber.ru/pipermail/ejabberd/2005-July/001252.html). So I disable it for the moment
		if 1 or iq_obj.getType() == 'error': #server doesn't support privacy lists
			# We use the old way which is not xmpp complient
			ptype = 'invisible'
			show = 'invisible'
		else:
			# active the privacy rule
			self.privacy_rules_supported = True
			self.activate_privacy_rule('invisible')
438
		prio = unicode(gajim.config.get_per('accounts', self.name, 'priority'))
439
		p = common.xmpp.Presence(typ = ptype, priority = prio, show = show)
440
		p = self.add_sha(p)
441
442
443
444
445
		if msg:
			p.setStatus(msg)
		if signed:
			p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
		self.connection.send(p)
446
		self.dispatch('STATUS', 'invisible')
447
448
		if initial:
			#ask our VCard
449
			self.request_vcard(None)
450
451
452

			#Get bookmarks from private namespace
			self.get_bookmarks()
453

454
			#Inform GUI we just signed in
nkour's avatar
nkour committed
455
			self.dispatch('SIGNED_IN', ())
456

457
458
459
460
461
462
463
	def test_gpg_passphrase(self, password):
		self.gpg.passphrase = password
		keyID = gajim.config.get_per('accounts', self.name, 'keyid')
		signed = self.gpg.sign('test', keyID)
		self.gpg.password = None
		return signed != 'BAD_PASSPHRASE'

464
465
466
467
468
	def get_signed_msg(self, msg):
		signed = ''
		keyID = gajim.config.get_per('accounts', self.name, 'keyid')
		if keyID and USE_GPG:
			use_gpg_agent = gajim.config.get('use_gpg_agent')
Yann Leboulanger's avatar
Yann Leboulanger committed
469
470
			if self.connected < 2 and self.gpg.passphrase is None and \
				not use_gpg_agent:
471
472
473
474
475
476
477
478
479
480
481
482
				# We didn't set a passphrase
				self.dispatch('ERROR', (_('OpenPGP passphrase was not given'),
					#%s is the account name here
					_('You will be connected to %s without OpenPGP.') % self.name))
			elif self.gpg.passphrase is not None or use_gpg_agent:
				signed = self.gpg.sign(msg, keyID)
				if signed == 'BAD_PASSPHRASE':
					signed = ''
					if self.connected < 2:
						self.dispatch('BAD_PASSPHRASE', ())
		return signed

dkirov's avatar
dkirov committed
483
484
485
	def connect_and_auth(self):
		self.on_connect_success = self._connect_success
		self.connect()
486

487
	def connect_and_init(self, show, msg, signed):
488
		self.continue_connect_info = [show, msg, signed]
dkirov's avatar
dkirov committed
489
490
		self.on_connect_auth = self._init_roster
		self.connect_and_auth()
491

dkirov's avatar
dkirov committed
492
493
	def _init_roster(self, con):
		self.connection = con
494
		if self.connection:
dkirov's avatar
dkirov committed
495
496
			con.set_send_timeout(self.keepalives, self.send_keepalive)
			self.connection.onreceive(None)
497
498
			# Ask meta_contacts before roster
			self.get_meta_contacts()
499

500
	def change_status(self, show, msg, sync = False, auto = False):
501
		if not show in STATUS_LIST:
502
			return -1
503
		sshow = helpers.get_xmpp_show(show)
504
		if not msg:
505
506
507
			msg = ''
		keyID = gajim.config.get_per('accounts', self.name, 'keyid')
		if keyID and USE_GPG and not msg:
508
			lowered_uf_status_msg = helpers.get_uf_show(show).lower()
509
510
			# do not show I'm invisible!
			if lowered_uf_status_msg == _('invisible'):
nkour's avatar
nkour committed
511
				lowered_uf_status_msg = _('offline')
512
			msg = _("I'm %s") % lowered_uf_status_msg
513
		signed = ''
514
515
		if not auto and not show == 'offline':
			signed = self.get_signed_msg(msg)
516
		self.status = msg
517
		if show != 'offline' and not self.connected:
518
			self.connect_and_init(show, msg, signed)
519

520
		elif show == 'offline' and self.connected:
521
			self.connected = 0
Yann Leboulanger's avatar
Yann Leboulanger committed
522
			if self.connection:
523
				self.on_purpose = True
524
				p = common.xmpp.Presence(typ = 'unavailable')
525
				p = self.add_sha(p)
526
527
				if msg:
					p.setStatus(msg)
dkirov's avatar
dkirov committed
528
				self.remove_all_transfers()
dkirov's avatar
dkirov committed
529
530
531
532
533
				self.time_to_reconnect = None
				self.connection.start_disconnect(p, self._on_disconnected)
			else:
				self.time_to_reconnect = None
				self._on_disconnected()
534

535
		elif show != 'offline' and self.connected:
536
537
538
			# dont'try to connect, when we are in state 'connecting'
			if self.connected == 1:
				return
539
			was_invisible = self.connected == STATUS_LIST.index('invisible')
540
541
			self.connected = STATUS_LIST.index(show)
			if show == 'invisible':
542
543
544
545
546
547
				self.send_invisible_presence(msg, signed)
				return
			if was_invisible and self.privacy_rules_supported:
				iq = self.build_privacy_rule('visible', 'allow')
				self.connection.send(iq)
				self.activate_privacy_rule('visible')
548
549
			prio = unicode(gajim.config.get_per('accounts', self.name,
				'priority'))
550
551
			p = common.xmpp.Presence(typ = None, priority = prio, show = sshow)
			p = self.add_sha(p)
552
553
554
555
			if msg:
				p.setStatus(msg)
			if signed:
				p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
nkour's avatar
nkour committed
556
			if self.connection:
nkour's avatar
nkour committed
557
				self.connection.send(p)
558
			self.dispatch('STATUS', show)
559

dkirov's avatar
dkirov committed
560
561
562
	def _on_disconnected(self):
		''' called when a disconnect request has completed successfully'''
		self.dispatch('STATUS', 'offline')
563
		gajim.proxy65_manager.disconnect(self.connection)
dkirov's avatar
dkirov committed
564
		self.connection = None
565

566
567
	def get_status(self):
		return STATUS_LIST[self.connected]
568

569
570
571
572
	def send_motd(self, jid, subject = '', msg = ''):
		if not self.connection:
			return
		msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject)
dkirov's avatar
dkirov committed
573
		self.connection.send(msg_iq)
574

575
	def send_message(self, jid, msg, keyID, type = 'chat', subject='',
576
	chatstate = None, msg_id = None, composing_jep = None, resource = None):
Yann Leboulanger's avatar
Yann Leboulanger committed
577
		if not self.connection:
578
			return
579
		if not msg and chatstate is None:
Yann Leboulanger's avatar
Yann Leboulanger committed
580
			return
581
582
583
		fjid = jid
		if resource:
			fjid += '/' + resource
584
585
586
587
		msgtxt = msg
		msgenc = ''
		if keyID and USE_GPG:
			#encrypt
Yann Leboulanger's avatar
Yann Leboulanger committed
588
			msgenc = self.gpg.encrypt(msg, [keyID])
589
			if msgenc:
590
591
592
593
594
				msgtxt = '[This message is encrypted]'
				lang = os.getenv('LANG')
				if lang is not None or lang != 'en': # we're not english
					msgtxt = _('[This message is encrypted]') +\
						' ([This message is encrypted])' # one  in locale and one en
595
		if type == 'chat':
596
			msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
597
598
		else:
			if subject:
599
				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
600
601
					typ = 'normal', subject = subject)
			else:
602
				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
603
					typ = 'normal')
604
		if msgenc:
605
			msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
606

607
		# chatstates - if peer supports jep85 or jep22, send chatstates
608
609
		# please note that the only valid tag inside a message containing a <body>
		# tag is the active event
610
		if chatstate is not None:
611
612
613
614
615
616
617
618
619
620
621
622
623
			if composing_jep == 'JEP-0085' or not composing_jep:
				# JEP-0085
				msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES)
			if composing_jep == 'JEP-0022' or not composing_jep:
				# JEP-0022
				chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT)
				if not msgtxt: # when no <body>, add <id>
					if not msg_id: # avoid putting 'None' in <id> tag
						msg_id = ''
					chatstate_node.setTagData('id', msg_id)
				# when msgtxt, requests JEP-0022 composing notification
				if chatstate is 'composing' or msgtxt: 
					chatstate_node.addChild(name = 'composing') 
624

dkirov's avatar
dkirov committed
625
		self.connection.send(msg_iq)
Yann Leboulanger's avatar
Yann Leboulanger committed
626
		no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
627
628
629
630
631
632
633
634
635
636
637
		ji = gajim.get_jid_without_resource(jid)
		if self.name not in no_log_for and ji not in no_log_for:
			log_msg = msg
			if subject:
				log_msg = _('Subject: %s\n%s') % (subject, msg)
			if log_msg:
				if type == 'chat':
					kind = 'chat_msg_sent'
				else:
					kind = 'single_msg_sent'
				gajim.logger.write(kind, jid, log_msg)
638
		self.dispatch('MSGSENT', (jid, msg, keyID))
639
640
641
642
643
644
	
	def send_stanza(self, stanza):
		''' send a stanza untouched '''
		if not self.connection:
			return
		self.connection.send(stanza)
645
	
646
647
648
649
650
	def ack_subscribed(self, jid):
		if not self.connection:
			return
		gajim.log.debug('ack\'ing subscription complete for %s' % jid)
		p = common.xmpp.Presence(jid, 'subscribe')
dkirov's avatar
dkirov committed
651
		self.connection.send(p)
652
653
654
655
656
657

	def ack_unsubscribed(self, jid):
		if not self.connection:
			return
		gajim.log.debug('ack\'ing unsubscription complete for %s' % jid)
		p = common.xmpp.Presence(jid, 'unsubscribe')
dkirov's avatar
dkirov committed
658
		self.connection.send(p)
659

660
661
	def request_subscription(self, jid, msg, name = '', groups = [],
	auto_auth = False):
662
663
664
		if not self.connection:
			return
		gajim.log.debug('subscription request for %s' % jid)
665
		if auto_auth:
666
			self.jids_for_auto_auth.append(jid)
667
668
669
670
671
672
673
674
675
676
677
		# RFC 3921 section 8.2
		infos = {'jid': jid}
		if name:
			infos['name'] = name
		iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER)
		q = iq.getTag('query')
		item = q.addChild('item', attrs = infos)
		for g in groups:
			item.addChild('group').setData(g)
		self.connection.send(iq)

678
679
		p = common.xmpp.Presence(jid, 'subscribe')
		p = self.add_sha(p)
680
681
		if not msg:
			msg = _('I would like to add you to my roster.')
682
		p.setStatus(msg)
dkirov's avatar
dkirov committed
683
		self.connection.send(p)
684

Yann Leboulanger's avatar
Yann Leboulanger committed
685
	def send_authorization(self, jid):
686
687
		if not self.connection:
			return
688
689
		p = common.xmpp.Presence(jid, 'subscribed')
		p = self.add_sha(p)
dkirov's avatar
dkirov committed
690
		self.connection.send(p)
691

Yann Leboulanger's avatar
Yann Leboulanger committed
692
	def refuse_authorization(self, jid):
693
694
		if not self.connection:
			return
695
696
		p = common.xmpp.Presence(jid, 'unsubscribed')
		p = self.add_sha(p)
dkirov's avatar
dkirov committed
697
		self.connection.send(p)
698

699
	def unsubscribe(self, jid, remove_auth = True):
700
701
		if not self.connection:
			return
702
		if remove_auth:
703
			self.connection.getRoster().delItem(jid)
Yann Leboulanger's avatar
Yann Leboulanger committed
704
705
		else:
			self.connection.getRoster().Unsubscribe(jid)
706
			self.update_contact(jid, '', [])
707

708
	def _continue_unsubscribe(self, con, iq_obj, agent):
dkirov's avatar
dkirov committed
709
710
711
		if iq_obj.getTag('error'):
			# error, probably not a real agent
			return
712
		self.connection.getRoster().delItem(agent)
713

714
715
716
	def unsubscribe_agent(self, agent):
		if not self.connection:
			return
717
718
719
		iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
		iq.getTag('query').setTag('remove')
		self.connection.SendAndCallForResponse(iq, self._continue_unsubscribe,
720
721
722
			{'agent': agent})
		return

723
	def update_contact(self, jid, name, groups):
724
		'''update roster item on jabber server'''
725
		if self.connection:
nkour's avatar
nkour committed
726
			self.connection.getRoster().setItem(jid = jid, name = name,
727
				groups = groups)
728
	
Yann Leboulanger's avatar
Yann Leboulanger committed
729
	def new_account(self, name, config, sync = False):
730
731
732
		# If a connection already exist we cannot create a new account
		if self.connection:
			return
dkirov's avatar
dkirov committed
733
734
735
736
737
		self._hostname = config['hostname']
		self.new_account_info = config
		self.name = name
		self.on_connect_success = self._on_new_account
		self.connect(config)
738

dkirov's avatar
dkirov committed
739
	def _on_new_account(self,con, con_type):
740
		if not con_type:
741
			self.dispatch('ACC_NOT_OK',
dkirov's avatar
dkirov committed
742
				(_('Could not connect to "%s"') % self._hostname))
743
			return
744
		self.connection = con
dkirov's avatar
dkirov committed
745
		common.xmpp.features_nb.getRegInfo(con, self._hostname)
746

747
748
749
	def account_changed(self, new_name):
		self.name = new_name

750
751
752
753
754
755
756
757
758
759
	def request_last_status_time(self, jid, resource):
		if not self.connection:
			return
		to_whom_jid = jid
		if resource:
			to_whom_jid += '/' + resource
		iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\
			common.xmpp.NS_LAST)
		self.connection.send(iq)

760
761
762
	def request_os_info(self, jid, resource):
		if not self.connection:
			return
Yann Leboulanger's avatar
Yann Leboulanger committed
763
		to_whom_jid = jid
764
		if resource:
Yann Leboulanger's avatar
Yann Leboulanger committed
765
			to_whom_jid += '/' + resource
766
		iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\
767
			common.xmpp.NS_VERSION)
dkirov's avatar
dkirov committed
768
		self.connection.send(iq)
769

770
771
772
773
774
775
776
	def get_settings(self):
		''' Get Gajim settings as described in JEP 0049 '''
		if not self.connection:
			return
		iq = common.xmpp.Iq(typ='get')
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
		iq3 = iq2.addChild(name='gajim', namespace='gajim:prefs')
dkirov's avatar
dkirov committed
777
		self.connection.send(iq)
778
779

	def get_bookmarks(self):
780
		'''Get Bookmarks from storage as described in JEP 0048'''
781
		self.bookmarks = [] #avoid multiple bookmarks when re-connecting
782
783
784
		if not self.connection:
			return
		iq = common.xmpp.Iq(typ='get')
785
786
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
		iq2.addChild(name='storage', namespace='storage:bookmarks')
dkirov's avatar
dkirov committed
787
		self.connection.send(iq)
788
789
790

	def store_bookmarks(self):
		''' Send bookmarks to the storage namespace '''
791
792
		if not self.connection:
			return
793
		iq = common.xmpp.Iq(typ='set')
nkour's avatar
nkour committed
794
795
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
		iq3 = iq2.addChild(name='storage', namespace='storage:bookmarks')
796
		for bm in self.bookmarks:
nkour's avatar
nkour committed
797
798
799
800
			iq4 = iq3.addChild(name = "conference")
			iq4.setAttr('jid', bm['jid'])
			iq4.setAttr('autojoin', bm['autojoin'])
			iq4.setAttr('name', bm['name'])
801
802
803
804
805
806
807
			# Only add optional elements if not empty
			# Note: need to handle both None and '' as empty
			#   thus shouldn't use "is not None"
			if bm['nick']:
				iq5 = iq4.setTagData('nick', bm['nick'])
			if bm['password']:
				iq5 = iq4.setTagData('password', bm['password'])
dkirov's avatar
dkirov committed
808
		self.connection.send(iq)
809

810
811
812
813
814
815
816
	def get_meta_contacts(self):
		'''Get meta_contacts list from storage as described in JEP 0049'''
		if not self.connection:
			return
		iq = common.xmpp.Iq(typ='get')
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
		iq2.addChild(name='gajim', namespace='gajim:metacontacts')
dkirov's avatar
dkirov committed
817
		self.connection.send(iq)
818
819
820
821
822
823
824
825
826
827

	def store_meta_contacts(self, children_list):
		''' Send meta contacts to the storage namespace '''
		if not self.connection:
			return
		iq = common.xmpp.Iq(typ='set')
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
		iq3 = iq2.addChild(name='gajim', namespace='gajim:metacontacts')
		for parent_jid in children_list:
			parent_tag = iq3.addChild(name='parent', attrs = {'name': parent_jid})
828
			for child_jid in children_list[parent_jid]:
829
				parent_tag.addChild(name='child', attrs = {'name': child_jid})
dkirov's avatar
dkirov committed
830
		self.connection.send(iq)
831

Yann Leboulanger's avatar
Yann Leboulanger committed
832
	def send_agent_status(self, agent, ptype):
833
834
		if not self.connection:
			return
835
		p = common.xmpp.Presence(to = agent, typ = ptype)
836
		p = self.add_sha(p)
dkirov's avatar
dkirov committed
837
		self.connection.send(p)
838

nkour's avatar
nkour committed
839
	def join_gc(self, nick, room, server, password):
840
841
		if not self.connection:
			return
842
		show = helpers.get_xmpp_show(STATUS_LIST[self.connected])
843
		p = common.xmpp.Presence(to = '%s@%s/%s' % (room, server, nick),
844
			show = show, status = self.status)
845
846
		if gajim.config.get('send_sha_in_gc_presence'):
			p = self.add_sha(p)
847
848
849
		t = p.setTag(common.xmpp.NS_MUC + ' x')
		if password:
			t.setTagData('password', password)
dkirov's avatar
dkirov committed
850
		self.connection.send(p)
851
		#last date/time in history to avoid duplicate
852
		# FIXME: This JID needs to be normalized; see #1364
853
		jid='%s@%s' % (room, server)
Yann Leboulanger's avatar
Yann Leboulanger committed
854
		last_log = gajim.logger.get_last_date_that_has_logs(jid)
855
		if last_log is None:
Yann Leboulanger's avatar
Yann Leboulanger committed
856
857
			last_log = 0
		self.last_history_line[jid]= last_log
858
859
860
861

	def send_gc_message(self, jid, msg):
		if not self.connection:
			return
862
		msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat')
dkirov's avatar
dkirov committed
863
		self.connection.send(msg_iq)
864
865
866
867
868
		self.dispatch('MSGSENT', (jid, msg))

	def send_gc_subject(self, jid, subject):
		if not self.connection:
			return
869
		msg_iq = common.xmpp.Message(jid,typ = 'groupchat', subject = subject)
dkirov's avatar
dkirov committed
870
		self.connection.send(msg_iq)
871

872
	def request_gc_config(self, room_jid):
873
		iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER,
874
			to = room_jid)
dkirov's avatar
dkirov committed
875
		self.connection.send(iq)
876

877
	def change_gc_nick(self, room_jid, nick):
878
879
		if not self.connection:
			return
880
		p = common.xmpp.Presence(to = '%s/%s' % (room_jid, nick))
881
		p = self.add_sha(p)
dkirov's avatar
dkirov committed
882
		self.connection.send(p)
883

884
885
886
	<