connection.py 34.2 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
		gajim.log.debug('reconnect')
108
		self.retrycount += 1
109
		signed = self.get_signed_msg(self.status)
dkirov's avatar
dkirov committed
110
		self.on_connect_auth = self._init_roster
111
		self.connect_and_init(self.old_show, self.status, signed)
112

113
114
115
116
		if self.connected < 2: #connection failed
			if self.retrycount > 10:
				self.connected = 0
				self.dispatch('STATUS', 'offline')
117
				self.dispatch('ERROR',
118
119
120
121
122
				(_('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
123
				self.time_to_reconnect = 20
124
			else:
dkirov's avatar
dkirov committed
125
126
				self.time_to_reconnect = 10
			gajim.idlequeue.set_alarm(self._reconnect_alarm, self.time_to_reconnect)
127
		else:
dkirov's avatar
dkirov committed
128
			# reconnect succeeded
129
130
			self.time_to_reconnect = None
			self.retrycount = 0
131

132
	def _disconnectedReconnCB(self):
nkour's avatar
nkour committed
133
		'''Called when we are disconnected'''
134
135
136
137
138
139
		gajim.log.debug('disconnectedReconnCB')
		if not self.connection:
			return
		self.old_show = STATUS_LIST[self.connected]
		self.connected = 0
		self.dispatch('STATUS', 'offline')
140
		gajim.proxy65_manager.disconnect(self.connection)
141
142
143
144
145
		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
146
147
				self.time_to_reconnect = 10
				gajim.idlequeue.set_alarm(self._reconnect_alarm, 10)
148
			else:
149
				self.dispatch('ERROR',
150
151
152
153
				(_('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
154
	
Yann Leboulanger's avatar
Yann Leboulanger committed
155
	def _event_dispatcher(self, realm, event, data):
156
		if realm == common.xmpp.NS_REGISTER:
dkirov's avatar
dkirov committed
157
			if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:
158
				# data is (agent, DataFrom, is_form)
159
160
				if self.new_account_info and\
				self.new_account_info['hostname'] == data[0]:
Yann Leboulanger's avatar
Yann Leboulanger committed
161
					#it's a new account
162
163
164
165
166
167
					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
168
169
170
					req = data[1].asDict()
					req['username'] = self.new_account_info['name']
					req['password'] = self.new_account_info['password']
dkirov's avatar
dkirov committed
171
					def _on_register_result(result):
172
173
						if not common.xmpp.isResultNode(result):
							self.dispatch('ACC_NOT_OK', (result.getError()))
dkirov's avatar
dkirov committed
174
175
176
177
178
179
180
181
182
183
184
185
							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
186
					common.xmpp.features_nb.register(self.connection, data[0],
dkirov's avatar
dkirov committed
187
						req, _on_register_result)
188
					return
189
190
191
192
193
				if not data[1]: # wrong answer
					self.dispatch('ERROR', (_('Invalid answer'),
						_('Transport %s answered wrongly to register request.') % \
						data[0]))
					return
194
195
196
197
198
199
				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))
200
201
		elif realm == '':
			if event == common.xmpp.transports.DATA_RECEIVED:
202
				self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
203
			elif event == common.xmpp.transports.DATA_SENT:
204
				self.dispatch('STANZA_SENT', unicode(data))
205

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

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

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

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

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

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

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

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

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

dkirov's avatar
dkirov committed
341
342
	def _connect_success(self, con, con_type):
		if not self.connected: # We went offline during connecting process
343
			return None, ''
dkirov's avatar
dkirov committed
344
		self.hosts = []
345
		if not con_type:
dkirov's avatar
dkirov committed
346
347
348
349
350
351
			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))
352
353
		# Ask metacontacts before roster
		self.get_metacontacts()
dkirov's avatar
dkirov committed
354
355
356
357
		self._register_handlers(con, con_type)
		return True

	def _register_handlers(self, con, con_type):
358
		self.peerhost = con.get_peerhost()
359
360
		# notify the gui about con_type
		self.dispatch('CON_TYPE', con_type)
361
		ConnectionHandlers._register_handlers(self, con, con_type)
362
363
364
		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
365
366
		self.connection = con
		con.auth(name, self.password, resource, 1, self.__on_auth)
367

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

	def quit(self, kill_core):
		if kill_core:
			if self.connected > 1:
				self.connected = 0
Yann Leboulanger's avatar
Yann Leboulanger committed
409
				self.connection.disconnect()
dkirov's avatar
dkirov committed
410
				self.time_to_reconnect = None
411
412
			return

413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
	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})
432

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

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

461
			#Inform GUI we just signed in
nkour's avatar
nkour committed
462
			self.dispatch('SIGNED_IN', ())
463

464
465
466
467
468
469
470
	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'

471
472
473
474
475
	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
476
477
			if self.connected < 2 and self.gpg.passphrase is None and \
				not use_gpg_agent:
478
479
480
481
482
483
484
485
486
487
488
489
				# 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
490
491
	def connect_and_auth(self):
		self.on_connect_success = self._connect_success
492
		self.on_connect_failure = self._connect_failure
dkirov's avatar
dkirov committed
493
		self.connect()
494

495
	def connect_and_init(self, show, msg, signed):
496
		self.continue_connect_info = [show, msg, signed]
dkirov's avatar
dkirov committed
497
498
		self.on_connect_auth = self._init_roster
		self.connect_and_auth()
499

dkirov's avatar
dkirov committed
500
501
	def _init_roster(self, con):
		self.connection = con
502
		if self.connection:
dkirov's avatar
dkirov committed
503
504
			con.set_send_timeout(self.keepalives, self.send_keepalive)
			self.connection.onreceive(None)
505
506
			# Ask metacontacts before roster
			self.get_metacontacts()
507

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

528
		elif show == 'offline' and self.connected:
529
			self.connected = 0
Yann Leboulanger's avatar
Yann Leboulanger committed
530
			if self.connection:
531
				self.on_purpose = True
532
				p = common.xmpp.Presence(typ = 'unavailable')
533
				p = self.add_sha(p)
534
535
				if msg:
					p.setStatus(msg)
dkirov's avatar
dkirov committed
536
				self.remove_all_transfers()
dkirov's avatar
dkirov committed
537
538
539
540
541
				self.time_to_reconnect = None
				self.connection.start_disconnect(p, self._on_disconnected)
			else:
				self.time_to_reconnect = None
				self._on_disconnected()
542

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

dkirov's avatar
dkirov committed
568
569
570
	def _on_disconnected(self):
		''' called when a disconnect request has completed successfully'''
		self.dispatch('STATUS', 'offline')
571
		gajim.proxy65_manager.disconnect(self.connection)
dkirov's avatar
dkirov committed
572
		self.connection = None
573

574
575
	def get_status(self):
		return STATUS_LIST[self.connected]
576

577
578
579
580
	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
581
		self.connection.send(msg_iq)
582

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

615
		# chatstates - if peer supports jep85 or jep22, send chatstates
616
617
		# please note that the only valid tag inside a message containing a <body>
		# tag is the active event
618
		if chatstate is not None:
619
620
621
622
623
624
625
626
627
628
629
630
631
			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') 
632

dkirov's avatar
dkirov committed
633
		self.connection.send(msg_iq)
Yann Leboulanger's avatar
Yann Leboulanger committed
634
		no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
635
636
637
638
639
640
641
642
643
644
645
		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)
646
		self.dispatch('MSGSENT', (jid, msg, keyID))
647
648
649
650
651
652
	
	def send_stanza(self, stanza):
		''' send a stanza untouched '''
		if not self.connection:
			return
		self.connection.send(stanza)
653
	
654
655
656
657
658
	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
659
		self.connection.send(p)
660
661
662
663
664
665

	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
666
		self.connection.send(p)
667

668
669
	def request_subscription(self, jid, msg, name = '', groups = [],
	auto_auth = False):
670
671
672
		if not self.connection:
			return
		gajim.log.debug('subscription request for %s' % jid)
673
		if auto_auth:
674
			self.jids_for_auto_auth.append(jid)
675
676
677
678
679
680
681
682
683
684
685
		# 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)

686
687
		p = common.xmpp.Presence(jid, 'subscribe')
		p = self.add_sha(p)
688
689
		if not msg:
			msg = _('I would like to add you to my roster.')
690
		p.setStatus(msg)
dkirov's avatar
dkirov committed
691
		self.connection.send(p)
692

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

Yann Leboulanger's avatar
Yann Leboulanger committed
700
	def refuse_authorization(self, jid):
701
702
		if not self.connection:
			return
703
704
		p = common.xmpp.Presence(jid, 'unsubscribed')
		p = self.add_sha(p)
dkirov's avatar
dkirov committed
705
		self.connection.send(p)
706

707
	def unsubscribe(self, jid, remove_auth = True):
708
709
		if not self.connection:
			return
710
		if remove_auth:
711
			self.connection.getRoster().delItem(jid)
Yann Leboulanger's avatar
Yann Leboulanger committed
712
713
		else:
			self.connection.getRoster().Unsubscribe(jid)
714
			self.update_contact(jid, '', [])
715

716
	def _continue_unsubscribe(self, con, iq_obj, agent):
dkirov's avatar
dkirov committed
717
718
719
		if iq_obj.getTag('error'):
			# error, probably not a real agent
			return
720
		self.connection.getRoster().delItem(agent)
721

722
723
724
	def unsubscribe_agent(self, agent):
		if not self.connection:
			return
725
726
727
		iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
		iq.getTag('query').setTag('remove')
		self.connection.SendAndCallForResponse(iq, self._continue_unsubscribe,
728
729
730
			{'agent': agent})
		return

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

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

756
757
758
	def account_changed(self, new_name):
		self.name = new_name

759
760
761
762
763
764
765
766
767
768
	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)

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

779
780
781
782
783
784
785
	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
786
		self.connection.send(iq)
787
788

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

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

819
820
	def get_metacontacts(self):
		'''Get metacontacts list from storage as described in JEP 0049'''
821
822
823
824
		if not self.connection:
			return
		iq = common.xmpp.Iq(typ='get')
		iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
825
826
827
828
829
830
		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')
831
		iq2.addChild(name='gajim', namespace='gajim:metacontacts')
dkirov's avatar
dkirov committed
832
		self.connection.send(iq)
833

834
	def store_metacontacts(self, tags_list):
835
836
837
838
839
		''' 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')
840
841
842
843
844
845
846
847
		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
848
		self.connection.send(iq)
849

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

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

	def send_gc_message(self, jid, msg):
		if not self.connection:
			return