connection.py 34.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.on_connect_failure = None
91
		self.retrycount = 0
92
		self.jids_for_auto_auth = [] # list of jid to auto-authorize
93
		
94
	# END __init__
95
	def put_event(self, ev):
dkirov's avatar
dkirov committed
96
97
		if gajim.handlers.has_key(ev[0]):
			gajim.handlers[ev[0]](self.name, ev[1])
98

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

103
104

	def _reconnect(self):
105
106
		# Do not try to reco while we are already trying
		self.time_to_reconnect = None
107
		if self.connected < 2: #connection failed
108
109
110
111
112
			gajim.log.debug('reconnect')
			self.retrycount += 1
			signed = self.get_signed_msg(self.status)
			self.on_connect_auth = self._init_roster
			self.connect_and_init(self.old_show, self.status, signed)
113
		else:
dkirov's avatar
dkirov committed
114
			# reconnect succeeded
115
116
			self.time_to_reconnect = None
			self.retrycount = 0
117

118
	def _disconnectedReconnCB(self):
nkour's avatar
nkour committed
119
		'''Called when we are disconnected'''
120
		gajim.log.debug('disconnectedReconnCB')
121
122
123
124
		if self.connected > 1:
			# we cannot change our status to offline or connectiong
			# after we auth to server
			self.old_show = STATUS_LIST[self.connected]
125
126
		self.connected = 0
		self.dispatch('STATUS', 'offline')
127
128
129
130
131
		if self.connection:
			# make sure previous connection is completely closed
			gajim.proxy65_manager.disconnect(self.connection)
			self.connection.disconnect()
			self.connection = None
132
133
134
135
		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
136
				self.time_to_reconnect = 10
137
138
139
140
141
142
143
144
145
				# this check has moved from _reconnect method
				if self.retrycount > 5:
					self.time_to_reconnect = 20
				else:
					self.time_to_reconnect = 10
				gajim.idlequeue.set_alarm(self._reconnect_alarm, 
										self.time_to_reconnect)
			elif self.on_connect_failure:
				self.on_connect_failure()
146
			else:
147
				self.dispatch('ERROR',
148
149
150
151
				(_('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
152
	
Yann Leboulanger's avatar
Yann Leboulanger committed
153
	def _event_dispatcher(self, realm, event, data):
154
		if realm == common.xmpp.NS_REGISTER:
dkirov's avatar
dkirov committed
155
			if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:
156
				# data is (agent, DataFrom, is_form)
157
158
				if self.new_account_info and\
				self.new_account_info['hostname'] == data[0]:
Yann Leboulanger's avatar
Yann Leboulanger committed
159
					#it's a new account
160
161
162
163
164
165
					if not data[1]: # wrong answer
						print self.connection.lastErr
						self.dispatch('ACC_NOT_OK', (
							_('Transport %s answered wrongly to register request.') % \
							data[0]))
						return
Yann Leboulanger's avatar
Yann Leboulanger committed
166
167
168
					req = data[1].asDict()
					req['username'] = self.new_account_info['name']
					req['password'] = self.new_account_info['password']
dkirov's avatar
dkirov committed
169
					def _on_register_result(result):
170
171
						if not common.xmpp.isResultNode(result):
							self.dispatch('ACC_NOT_OK', (result.getError()))
dkirov's avatar
dkirov committed
172
173
174
175
176
177
178
179
180
181
182
183
							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
				if not data[1]: # wrong answer
					self.dispatch('ERROR', (_('Invalid answer'),
						_('Transport %s answered wrongly to register request.') % \
						data[0]))
					return
192
193
194
195
196
197
				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))
198
199
		elif realm == '':
			if event == common.xmpp.transports.DATA_RECEIVED:
200
				self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
201
			elif event == common.xmpp.transports.DATA_SENT:
202
				self.dispatch('STANZA_SENT', unicode(data))
203

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
	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

227
	def connect(self, data = None):
dkirov's avatar
dkirov committed
228
		''' Start a connection to the Jabber server.
229
230
231
		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
232
		use_custom_host), '''
Yann Leboulanger's avatar
Yann Leboulanger committed
233
		if self.connection:
234
			return self.connection, ''
Yann Leboulanger's avatar
Yann Leboulanger committed
235

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

262
		#create connection if it doesn't already exist
263
		self.connected = 1
Yann Leboulanger's avatar
Yann Leboulanger committed
264
		if p and p in gajim.config.get_per('proxies'):
265
266
267
268
			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')
269
270
		else:
			proxy = None
271

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

285
		hosts = []
286
		# SRV resolver
dkirov's avatar
dkirov committed
287
288
289
290
291
292
293
294
295
		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('', [])
296

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

dkirov's avatar
dkirov committed
303
304
	def connect_to_next_host(self):
		if len(self._hosts):
305
306
307
			if self.last_connection:
				self.last_connection.socket.disconnect()
				self.last_connection = None
308
				self.connection = None
Yann Leboulanger's avatar
Yann Leboulanger committed
309
			if gajim.verbose:
310
311
				con = common.xmpp.NonBlockingClient(self._hostname, caller = self,
					on_connect = self.on_connect_success,
dkirov's avatar
dkirov committed
312
					on_connect_failure = self.connect_to_next_host)
Yann Leboulanger's avatar
Yann Leboulanger committed
313
			else:
314
315
				con = common.xmpp.NonBlockingClient(self._hostname, debug = [], caller = self,
					on_connect = self.on_connect_success,
dkirov's avatar
dkirov committed
316
					on_connect_failure = self.connect_to_next_host)
317
			self.last_connection = con
dkirov's avatar
dkirov committed
318
319
320
321
322
323
			# 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)
324
			con.connect((host['host'], host['port']), proxy = self._proxy,
dkirov's avatar
dkirov committed
325
326
327
				secure = self._secure)
			return
		else:
328
329
330
331
332
			if self.retrycount > 10:
				self.retrycount = 0
				self.time_to_reconnect = None
				self.on_connect_failure()
			else:
333
				# try reconnect if connection has failed before auth to server
334
				self._disconnectedReconnCB()
dkirov's avatar
dkirov committed
335

336
	def _connect_failure(self, con_type = None):
337
		if not con_type:
dkirov's avatar
dkirov committed
338
339
			# we are not retrying, and not conecting
			if not self.retrycount and self.connected != 0:
340
341
				self.connected = 0
				self.dispatch('STATUS', 'offline')
dkirov's avatar
dkirov committed
342
				self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname,
343
					_('Check your connection or try again later.')))
344

dkirov's avatar
dkirov committed
345
346
	def _connect_success(self, con, con_type):
		if not self.connected: # We went offline during connecting process
347
348
			# FIXME - not possible, maybe it was when we used threads
			return
dkirov's avatar
dkirov committed
349
		self.hosts = []
350
		if not con_type:
dkirov's avatar
dkirov committed
351
352
353
			gajim.log.debug('Could not connect to %s:%s' % (self._current_host['host'],
				self._current_host['port']))
		self.connected_hostname = self._current_host['host']
354
		self.on_connect_failure = None
dkirov's avatar
dkirov committed
355
356
357
		con.RegisterDisconnectHandler(self._disconnectedReconnCB)
		gajim.log.debug(_('Connected to server %s:%s with %s') % (self._current_host['host'],
			self._current_host['port'], con_type))
358
359
		# Ask metacontacts before roster
		self.get_metacontacts()
dkirov's avatar
dkirov committed
360
361
362
363
		self._register_handlers(con, con_type)
		return True

	def _register_handlers(self, con, con_type):
364
		self.peerhost = con.get_peerhost()
365
366
		# notify the gui about con_type
		self.dispatch('CON_TYPE', con_type)
367
		ConnectionHandlers._register_handlers(self, con, con_type)
368
369
370
		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
371
372
		self.connection = con
		con.auth(name, self.password, resource, 1, self.__on_auth)
373

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

	def quit(self, kill_core):
		if kill_core:
			if self.connected > 1:
				self.connected = 0
415
				gajim.proxy65_manager.disconnect(self.connection)
Yann Leboulanger's avatar
Yann Leboulanger committed
416
				self.connection.disconnect()
dkirov's avatar
dkirov committed
417
				self.time_to_reconnect = None
418
419
			return

420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
	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})
439

440
441
442
443
444
445
446
447
448
449
450
451
	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')
452
		prio = unicode(gajim.config.get_per('accounts', self.name, 'priority'))
453
		p = common.xmpp.Presence(typ = ptype, priority = prio, show = show)
454
		p = self.add_sha(p)
455
456
457
458
459
		if msg:
			p.setStatus(msg)
		if signed:
			p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
		self.connection.send(p)
460
		self.dispatch('STATUS', 'invisible')
461
462
		if initial:
			#ask our VCard
463
			self.request_vcard(None)
464
465
466

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

468
			#Inform GUI we just signed in
nkour's avatar
nkour committed
469
			self.dispatch('SIGNED_IN', ())
470

471
472
473
474
475
476
477
	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'

478
479
480
481
482
	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
483
484
			if self.connected < 2 and self.gpg.passphrase is None and \
				not use_gpg_agent:
485
486
487
488
489
490
491
492
493
494
495
496
				# 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
497
498
	def connect_and_auth(self):
		self.on_connect_success = self._connect_success
499
		self.on_connect_failure = self._connect_failure
dkirov's avatar
dkirov committed
500
		self.connect()
501

502
	def connect_and_init(self, show, msg, signed):
503
		self.continue_connect_info = [show, msg, signed]
dkirov's avatar
dkirov committed
504
505
		self.on_connect_auth = self._init_roster
		self.connect_and_auth()
506

dkirov's avatar
dkirov committed
507
508
	def _init_roster(self, con):
		self.connection = con
509
		if self.connection:
dkirov's avatar
dkirov committed
510
511
			con.set_send_timeout(self.keepalives, self.send_keepalive)
			self.connection.onreceive(None)
512
513
			# Ask metacontacts before roster
			self.get_metacontacts()
514

515
	def change_status(self, show, msg, sync = False, auto = False):
516
		if not show in STATUS_LIST:
517
			return -1
518
		sshow = helpers.get_xmpp_show(show)
519
		if not msg:
520
521
522
			msg = ''
		keyID = gajim.config.get_per('accounts', self.name, 'keyid')
		if keyID and USE_GPG and not msg:
523
			lowered_uf_status_msg = helpers.get_uf_show(show).lower()
524
525
			# do not show I'm invisible!
			if lowered_uf_status_msg == _('invisible'):
nkour's avatar
nkour committed
526
				lowered_uf_status_msg = _('offline')
527
			msg = _("I'm %s") % lowered_uf_status_msg
528
		signed = ''
529
530
		if not auto and not show == 'offline':
			signed = self.get_signed_msg(msg)
531
		self.status = msg
532
		if show != 'offline' and not self.connected:
533
534
535
			# set old_show to requested 'show' in case we need to
			# recconect before we auth to server
			self.old_show = show
536
			self.connect_and_init(show, msg, signed)
537

538
		elif show == 'offline' and self.connected:
539
			self.connected = 0
Yann Leboulanger's avatar
Yann Leboulanger committed
540
			if self.connection:
541
				self.on_purpose = True
542
				p = common.xmpp.Presence(typ = 'unavailable')
543
				p = self.add_sha(p)
544
545
				if msg:
					p.setStatus(msg)
dkirov's avatar
dkirov committed
546
				self.remove_all_transfers()
dkirov's avatar
dkirov committed
547
548
549
550
551
				self.time_to_reconnect = None
				self.connection.start_disconnect(p, self._on_disconnected)
			else:
				self.time_to_reconnect = None
				self._on_disconnected()
552

553
		elif show != 'offline' and self.connected:
554
555
556
			# dont'try to connect, when we are in state 'connecting'
			if self.connected == 1:
				return
557
			was_invisible = self.connected == STATUS_LIST.index('invisible')
558
559
			self.connected = STATUS_LIST.index(show)
			if show == 'invisible':
560
561
562
563
564
565
				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')
566
567
			prio = unicode(gajim.config.get_per('accounts', self.name,
				'priority'))
568
569
			p = common.xmpp.Presence(typ = None, priority = prio, show = sshow)
			p = self.add_sha(p)
570
571
572
573
			if msg:
				p.setStatus(msg)
			if signed:
				p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
nkour's avatar
nkour committed
574
			if self.connection:
nkour's avatar
nkour committed
575
				self.connection.send(p)
576
			self.dispatch('STATUS', show)
577

dkirov's avatar
dkirov committed
578
579
580
	def _on_disconnected(self):
		''' called when a disconnect request has completed successfully'''
		self.dispatch('STATUS', 'offline')
581
		gajim.proxy65_manager.disconnect(self.connection)
dkirov's avatar
dkirov committed
582
		self.connection = None
583

584
585
	def get_status(self):
		return STATUS_LIST[self.connected]
586

587
588
589
590
	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
591
		self.connection.send(msg_iq)
592

593
	def send_message(self, jid, msg, keyID, type = 'chat', subject='',
594
	chatstate = None, msg_id = None, composing_jep = None, resource = None):
Yann Leboulanger's avatar
Yann Leboulanger committed
595
		if not self.connection:
596
			return
597
		if not msg and chatstate is None:
Yann Leboulanger's avatar
Yann Leboulanger committed
598
			return
599
600
601
		fjid = jid
		if resource:
			fjid += '/' + resource
602
603
604
605
		msgtxt = msg
		msgenc = ''
		if keyID and USE_GPG:
			#encrypt
Yann Leboulanger's avatar
Yann Leboulanger committed
606
			msgenc = self.gpg.encrypt(msg, [keyID])
607
			if msgenc:
608
609
610
611
612
				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
613
		if type == 'chat':
614
			msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
615
616
		else:
			if subject:
617
				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
618
619
					typ = 'normal', subject = subject)
			else:
620
				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
621
					typ = 'normal')
622
		if msgenc:
623
			msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
624

625
		# chatstates - if peer supports jep85 or jep22, send chatstates
626
627
		# please note that the only valid tag inside a message containing a <body>
		# tag is the active event
628
		if chatstate is not None:
629
630
631
632
633
634
635
636
637
638
639
640
641
			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') 
642

dkirov's avatar
dkirov committed
643
		self.connection.send(msg_iq)
Yann Leboulanger's avatar
Yann Leboulanger committed
644
		no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
645
646
647
648
649
650
651
652
653
654
655
		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)
656
		self.dispatch('MSGSENT', (jid, msg, keyID))
657
658
659
660
661
662
	
	def send_stanza(self, stanza):
		''' send a stanza untouched '''
		if not self.connection:
			return
		self.connection.send(stanza)
663
	
664
665
666
667
668
	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
669
		self.connection.send(p)
670
671
672
673
674
675

	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
676
		self.connection.send(p)
677

678
679
	def request_subscription(self, jid, msg, name = '', groups = [],
	auto_auth = False):
680
681
682
		if not self.connection:
			return
		gajim.log.debug('subscription request for %s' % jid)
683
		if auto_auth:
684
			self.jids_for_auto_auth.append(jid)
685
686
687
688
689
690
691
692
693
694
695
		# 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)

696
697
		p = common.xmpp.Presence(jid, 'subscribe')
		p = self.add_sha(p)
698
699
		if not msg:
			msg = _('I would like to add you to my roster.')
700
		p.setStatus(msg)
dkirov's avatar
dkirov committed
701
		self.connection.send(p)
702

Yann Leboulanger's avatar
Yann Leboulanger committed
703
	def send_authorization(self, jid):
704
705
		if not self.connection:
			return
706
707
		p = common.xmpp.Presence(jid, 'subscribed')
		p = self.add_sha(p)
dkirov's avatar
dkirov committed
708
		self.connection.send(p)
709

Yann Leboulanger's avatar
Yann Leboulanger committed
710
	def refuse_authorization(self, jid):
711
712
		if not self.connection:
			return
713
714
		p = common.xmpp.Presence(jid, 'unsubscribed')
		p = self.add_sha(p)
dkirov's avatar
dkirov committed
715
		self.connection.send(p)
716

717
	def unsubscribe(self, jid, remove_auth = True):
718
719
		if not self.connection:
			return
720
		if remove_auth:
721
			self.connection.getRoster().delItem(jid)
Yann Leboulanger's avatar
Yann Leboulanger committed
722
723
		else:
			self.connection.getRoster().Unsubscribe(jid)
724
			self.update_contact(jid, '', [])
725

726
727
728
	def unsubscribe_agent(self, agent):
		if not self.connection:
			return
729
730
		iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
		iq.getTag('query').setTag('remove')
731
732
		self.connection.send(iq)
		self.connection.getRoster().delItem(agent)
733

734
	def update_contact(self, jid, name, groups):
735
		'''update roster item on jabber server'''
736
		if self.connection:
nkour's avatar
nkour committed
737
			self.connection.getRoster().setItem(jid = jid, name = name,
738
				groups = groups)
739
	
Yann Leboulanger's avatar
Yann Leboulanger committed
740
	def new_account(self, name, config, sync = False):
741
742
743
		# If a connection already exist we cannot create a new account
		if self.connection:
			return
dkirov's avatar
dkirov committed
744
745
746
747
		self._hostname = config['hostname']
		self.new_account_info = config
		self.name = name
		self.on_connect_success = self._on_new_account
748
		self.on_connect_failure = self._on_new_account
dkirov's avatar
dkirov committed
749
		self.connect(config)
750

751
	def _on_new_account(self, con = None, con_type = None):
752
		if not con_type:
753
			self.dispatch('ACC_NOT_OK',
dkirov's avatar
dkirov committed
754
				(_('Could not connect to "%s"') % self._hostname))
755
			return
756
		self.on_connect_failure = None
757
		self.connection = con
dkirov's avatar
dkirov committed
758
		common.xmpp.features_nb.getRegInfo(con, self._hostname)
759

760
761
762
	def account_changed(self, new_name):
		self.name = new_name

763
764
765
766
767
768
769
770
771
772
	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)

773
774
775
	def request_os_info(self, jid, resource):
		if not self.connection:
			return
Yann Leboulanger's avatar
Yann Leboulanger committed
776
		to_whom_jid = jid
777
		if resource:
Yann Leboulanger's avatar
Yann Leboulanger committed
778
			to_whom_jid += '/' + resource
779
		iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\
780
			common.xmpp.NS_VERSION)
dkirov's avatar
dkirov committed
781
		self.connection.send(iq)
782

783
784
785
786
787
788
789
	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
790
		self.connection.send(iq)
791
792

	def get_bookmarks(self):
793
		'''Get Bookmarks from storage as described in JEP 0048'''
794
		self.bookmarks = [] #avoid multiple bookmarks when re-connecting
795
796
797
		if not self.connection:
			return
		iq = common.xmpp.Iq(typ='get')
798
799
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
		iq2.addChild(name='storage', namespace='storage:bookmarks')
dkirov's avatar
dkirov committed
800
		self.connection.send(iq)
801
802
803

	def store_bookmarks(self):
		''' Send bookmarks to the storage namespace '''
804
805
		if not self.connection:
			return
806
		iq = common.xmpp.Iq(typ='set')
nkour's avatar
nkour committed
807
808
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
		iq3 = iq2.addChild(name='storage', namespace='storage:bookmarks')
809
		for bm in self.bookmarks:
nkour's avatar
nkour committed
810
811
812
813
			iq4 = iq3.addChild(name = "conference")
			iq4.setAttr('jid', bm['jid'])
			iq4.setAttr('autojoin', bm['autojoin'])
			iq4.setAttr('name', bm['name'])
814
815
816
817
818
819
820
			# 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
821
		self.connection.send(iq)
822

823
824
	def get_metacontacts(self):
		'''Get metacontacts list from storage as described in JEP 0049'''
825
826
827
828
		if not self.connection:
			return
		iq = common.xmpp.Iq(typ='get')
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
829
830
831
832
833
834
		iq2.addChild(name='storage', namespace='storage:metacontacts')
		self.connection.send(iq)

		#FIXME: remove the old infos, remove that before 0.10
		iq = common.xmpp.Iq(typ='set')
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
835
		iq2.addChild(name='gajim', namespace='gajim:metacontacts')
dkirov's avatar
dkirov committed
836
		self.connection.send(iq)
837

838
	def store_metacontacts(self, tags_list):
839
840
841
842
843
		''' 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')
844
845
846
847
848
849
850
851
		iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts')
		for tag in tags_list:
			for data in tags_list[tag]:
				jid = data['jid']
				dict_ = {'jid': jid, 'tag': tag}
				if data.has_key('priority'):
					dict_['priority'] = data['priority']
				iq3.addChild(name = 'meta', attrs = dict_)
dkirov's avatar
dkirov committed
852
		self.connection.send(iq)
853

Yann Leboulanger's avatar
Yann Leboulanger committed
854
	def send_agent_status(self, agent, ptype):
855
856
		if not self.connection:
			return
857
		p = common.xmpp.Presence(to = agent, typ = ptype)
858
		p = self.add_sha(p)
dkirov's avatar
dkirov committed
859
		self.connection.send(p)
860

nkour's avatar
nkour committed
861
	def join_gc(self, nick, room, server, password):
862
863
		if not self.connection:
			return
864
		show = helpers.get_xmpp_show(STATUS_LIST[self.connected])
865
866
867
		if show == 'invisible':
			# Never join a room when invisible
			return
868
		p = common.xmpp.Presence(to = '%s@%s/%s' % (room, server, nick),
869
			show = show, status = self.status)
870
871
		if gajim.config.get('send_sha_in_gc_presence'):
			p = self.add_sha(p)
872
873
874
		t = p.setTag(common.xmpp.NS_MUC + ' x')
		if password:
			t.setTagData('password', password)
dkirov's avatar
dkirov committed
875
		self.connection.send(p)
876
		#last date/time in history to avoid duplicate
877
		# FIXME: This JID needs to be normalized; see #1364
878
		jid='%s@%s' % (room, server)
879
		last_log = gajim.logger.get_last_date_that_has_logs(jid, is_room = True)
880
		if last_log is None:
Yann Leboulanger's avatar
Yann Leboulanger committed
881
882
			last_log = 0
		self.last_history_line[jid]= last_log
883
884
885
886

	def send_gc_message(self, jid, msg):
		if not self.connection:
			return
887
		msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat')
dkirov's avatar
dkirov committed
888
		self.connection.