Skip to content
Snippets Groups Projects
connection_zeroconf.py 16.5 KiB
Newer Older
##	common/zeroconf/connection_zeroconf.py
sb's avatar
sb committed
##
## Contributors for this file:
##	- Yann Le Boulanger <asterix@lagaule.org>
##	- Nikos Kouremenos <nkour@jabber.org>
##	- Dimitur Kirov <dkirov@gmail.com>
##	- Travis Shirk <travis@pobox.com>
##  - Stefan Bethge <stefan@lanpartei.de>
sb's avatar
sb committed
##
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
##                         Vincent Hanquez <tab@snarc.org>
## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
sb's avatar
sb committed
##                    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>
##                    Stefan Bethge <stefan@lanpartei.de>
sb's avatar
sb committed
##
## 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.
##


import os
import random
random.seed()

import signal
if os.name != 'nt':
	signal.signal(signal.SIGPIPE, signal.SIG_DFL)
import getpass
sb's avatar
sb committed
import gobject
import notify
sb's avatar
sb committed

sb's avatar
sb committed
from common import helpers
from common import gajim
from common import GnuPG
sb's avatar
sb committed
from common.zeroconf import connection_handlers_zeroconf
from common.zeroconf import client_zeroconf
sb's avatar
sb committed
from connection_handlers_zeroconf import *
sb's avatar
sb committed
USE_GPG = GnuPG.USE_GPG

class ConnectionZeroconf(ConnectionHandlersZeroconf):
	'''Connection class'''
	def __init__(self, name):
		ConnectionHandlersZeroconf.__init__(self)
		# system username
		self.username = None
sb's avatar
sb committed
		self.name = name
		self.connected = 0 # offline
sb's avatar
sb committed
		self.connection = None
sb's avatar
sb committed
		self.gpg = None
		self.is_zeroconf = True
		self.privacy_rules_supported = False
sb's avatar
sb committed
		self.status = ''
		self.old_show = ''
dkirov's avatar
dkirov committed
		self.priority = 0
sb's avatar
sb committed
	
		self.call_resolve_timeout = False
		
		#self.time_to_reconnect = None
		#self.new_account_info = None
		self.bookmarks = []
sb's avatar
sb committed

		#we don't need a password, but must be non-empty
		self.password = 'zeroconf'
		self.autoconnect = False
		self.sync_with_global_status = True
		self.no_log_for = False

sb's avatar
sb committed
		# Do we continue connection when we get roster (send presence,get vcard...)
		self.continue_connect_info = None
		if USE_GPG:
			self.gpg = GnuPG.GnuPG()
			gajim.config.set('usegpg', True)
		else:
			gajim.config.set('usegpg', False)
		
		self.get_config_values_or_default()
		self.muc_jid = {} # jid of muc server for each transport type
		self.vcard_supported = False
	def _on_name_conflictCB(self, alt_name):
		self.disconnect()
		self.dispatch('STATUS', 'offline')
		self.dispatch('ZC_NAME_CONFLICT', alt_name)

	def get_config_values_or_default(self):
		''' get name, host, port from config, or 
		create zeroconf account with default values'''
		if not self.username:
			self.username = unicode(getpass.getuser())
			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name', self.username)
			self.username = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name')

		if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'):
			print 'Creating zeroconf account'
			gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME)
			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect', True)
			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for', '')
			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password', 'zeroconf')
			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status', True)

			#XXX make sure host is US-ASCII
			self.host = unicode(socket.gethostname())
			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', self.host)
			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port', 5298)
			gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'is_zeroconf', True)
		self.host = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname')
		self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
		self.autoconnect = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect')
		self.sync_with_global_status = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
		self.no_log_for = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for')
		self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name')
		self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name')
		self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id')
		self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
sb's avatar
sb committed
	# END __init__
sb's avatar
sb committed
	def dispatch(self, event, data):
		if gajim.handlers.has_key(event):
			gajim.handlers[event](self.name, data)
sb's avatar
sb committed

	def _reconnect(self):
		gajim.log.debug('reconnect')
		signed = self.get_signed_msg(self.status)
sb's avatar
sb committed
	def quit(self, kill_core):
		if kill_core and self.connected > 1:
sb's avatar
sb committed
			self.disconnect()
sb's avatar
sb committed
	
sb's avatar
sb committed
	def disable_account(self):
		self.disconnect()

sb's avatar
sb committed
	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'

	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')
			if self.connected < 2 and self.gpg.passphrase is None and \
				not use_gpg_agent:
				# 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
sb's avatar
sb committed
	def _on_resolve_timeout(self):
		if self.connected:
dkirov's avatar
dkirov committed
			self.connection.resolve_all()
			diffs = self.roster.getDiffs()
			for key in diffs:
				self.roster.setItem(key)
				self.dispatch('ROSTER_INFO', (key, self.roster.getName(key),
							'both', 'no', self.roster.getGroups(key)))
				self.dispatch('NOTIFY', (key, self.roster.getStatus(key),
							self.roster.getMessage(key), 'local', 0, None, 0))
sb's avatar
sb committed
				#XXX open chat windows don't get refreshed (full name), add that
sb's avatar
sb committed
		return self.call_resolve_timeout

	# callbacks called from zeroconf	
	def _on_new_service(self,jid):
		self.roster.setItem(jid)
		self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
		self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0))
	def _on_remove_service(self, jid):
sb's avatar
sb committed
		# 'NOTIFY' (account, (jid, status, status message, resource, priority,
		# keyID, timestamp))
		self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0))
sb's avatar
sb committed

	def _on_disconnected(self):
		self.disconnect()
		self.dispatch('STATUS', 'offline')
		self.dispatch('CONNECTION_LOST',
			(_('Connection with account "%s" has been lost') % self.name,
			_('To continue sending and receiving messages, you will need to reconnect.')))
		self.status = 'offline'
		self.disconnect()
	def connect(self, show = 'online', msg = ''):
		self.get_config_values_or_default()
dkirov's avatar
dkirov committed
		if not self.connection:
			self.connection = client_zeroconf.ClientZeroconf(self)
			if not self.connection.test_avahi():
				self.dispatch('STATUS', 'offline')
				self.status = 'offline'
				self.dispatch('CONNECTION_LOST',
					(_('Could not connect to "%s"') % self.name,
					_('Please check if Avahi is installed.')))
				self.disconnect()
dkirov's avatar
dkirov committed
				return
			result = self.connection.connect(show, msg)
			if not result:
				self.dispatch('STATUS', 'offline')
dkirov's avatar
dkirov committed
				self.status = 'offline'
				if result is False:
					self.dispatch('CONNECTION_LOST',
						(_('Could not start local service'),
						_('Unable to bind to port %d.' % self.port)))
				else: # result is None
					self.dispatch('CONNECTION_LOST',
					(_('Could not start local service'),
					_('Please check if avahi-daemon is running.')))
				self.disconnect()
dkirov's avatar
dkirov committed
				return
		else:
			self.connection.announce()
		self.roster = self.connection.getRoster()
		self.dispatch('ROSTER', self.roster)
dkirov's avatar
dkirov committed
		#display contacts already detected and resolved
		for jid in self.roster.keys():
			self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
			self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0))
dkirov's avatar
dkirov committed
		self.connected = STATUS_LIST.index(show)
dkirov's avatar
dkirov committed
		# refresh all contacts data every five seconds
		self.call_resolve_timeout = True
		gobject.timeout_add(5000, self._on_resolve_timeout)
		return True

	def disconnect(self, on_purpose = False):
		self.connected = 0
		self.time_to_reconnect = None
		if self.connection:
dkirov's avatar
dkirov committed
			self.connection.disconnect()
			self.connection = None
			# stop calling the timeout
			self.call_resolve_timeout = False
dkirov's avatar
dkirov committed
	def reannounce(self):
		if self.connected:
			txt = {}
			txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name')
			txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name')
			txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id')
			txt['email'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
dkirov's avatar
dkirov committed
			self.connection.reannounce(txt)

	def update_details(self):
		if self.connection:
			port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
sb's avatar
sb committed
			if port != self.port:
				self.port = port
				last_msg = self.connection.last_msg
				self.disconnect()
				if not self.connect(self.status, last_msg):
					return
				if self.status != 'invisible':
					self.connection.announce()
dkirov's avatar
dkirov committed
			else:
				self.reannounce()
sb's avatar
sb committed
	def change_status(self, show, msg, sync = False, auto = False):
		if not show in STATUS_LIST:
			return -1
		self.status = show
		check = True		#to check for errors from zeroconf
		# 'connect'
sb's avatar
sb committed
		if show != 'offline' and not self.connected:
			if not self.connect(show, msg):
dkirov's avatar
dkirov committed
				return
dkirov's avatar
dkirov committed
				check = self.connection.announce()
dkirov's avatar
dkirov committed
				self.connected = STATUS_LIST.index(show)
sb's avatar
sb committed

		# 'disconnect'
sb's avatar
sb committed
		elif show == 'offline' and self.connected:
			self.disconnect()
			self.dispatch('STATUS', 'offline')
sb's avatar
sb committed
		elif show != 'offline' and self.connected:
			was_invisible = self.connected == STATUS_LIST.index('invisible')
			self.connected = STATUS_LIST.index(show)
			if show == 'invisible':
dkirov's avatar
dkirov committed
				check = check and self.connection.remove_announce()
dkirov's avatar
dkirov committed
				check = check and self.connection.announce()
			if self.connection and not show == 'invisible':
dkirov's avatar
dkirov committed
				check = check and self.connection.set_show_msg(show, msg)
		#stay offline when zeroconf does something wrong
		if check:
			self.dispatch('STATUS', show)
		else:
			# show notification that avahi, or system bus is down	
			self.dispatch('STATUS', 'offline')
			self.status = 'offline'
			self.dispatch('CONNECTION_LOST',
				(_('Could not change status of account "%s"') % self.name,
				_('Please check if avahi-daemon is running.')))
			
sb's avatar
sb committed
	def get_status(self):
		return STATUS_LIST[self.connected]

	def send_message(self, jid, msg, keyID, type = 'chat', subject='',
	chatstate = None, msg_id = None, composing_jep = None, resource = None, 
	user_nick = None):
sb's avatar
sb committed
		if not self.connection:
			return
		if not msg and chatstate is None:
			return
sb's avatar
sb committed
		msgtxt = msg
		msgenc = ''
		if keyID and USE_GPG:
			#encrypt
			msgenc = self.gpg.encrypt(msg, [keyID])
			if msgenc:
				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
sb's avatar
sb committed
		if type == 'chat':
			msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
sb's avatar
sb committed
		else:
			if subject:
				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
					typ = 'normal', subject = subject)
			else:
				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
					typ = 'normal')
sb's avatar
sb committed
		if msgenc:
			msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
sb's avatar
sb committed
		# chatstates - if peer supports jep85 or jep22, send chatstates
		# please note that the only valid tag inside a message containing a <body>
		# tag is the active event
		if chatstate is not None:
			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') 
		
		self.connection.send(msg_iq)
sb's avatar
sb committed
		no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
		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)
sb's avatar
sb committed
		self.dispatch('MSGSENT', (jid, msg, keyID))
sb's avatar
sb committed
	def send_stanza(self, stanza):
		print 'connection_zeroconf.py: send_stanza'
sb's avatar
sb committed
		if not self.connection:
			return
		#self.connection.send(stanza)
sb's avatar
sb committed
	
	def ack_subscribed(self, jid):
		gajim.log.debug('This should not happen (ack_subscribed)')
sb's avatar
sb committed

	def ack_unsubscribed(self, jid):
		gajim.log.debug('This should not happen (ack_unsubscribed)')
sb's avatar
sb committed

	def request_subscription(self, jid, msg = '', name = '', groups = [],
	auto_auth = False):
		gajim.log.debug('This should not happen (request_subscription)')
sb's avatar
sb committed

	def send_authorization(self, jid):
		gajim.log.debug('This should not happen (send_authorization)')
sb's avatar
sb committed

	def refuse_authorization(self, jid):
		gajim.log.debug('This should not happen (refuse_authorization)')
sb's avatar
sb committed

	def unsubscribe(self, jid, remove_auth = True):
		gajim.log.debug('This should not happen (unsubscribe)')
sb's avatar
sb committed

	def unsubscribe_agent(self, agent):
		gajim.log.debug('This should not happen (unsubscribe_agent)')
sb's avatar
sb committed

	def update_contact(self, jid, name, groups):	
sb's avatar
sb committed
		if self.connection:
			self.connection.getRoster().setItem(jid = jid, name = name,
				groups = groups)
	
	def new_account(self, name, config, sync = False):
		gajim.log.debug('This should not happen (new_account)')
sb's avatar
sb committed

	def _on_new_account(self, con = None, con_type = None):
		gajim.log.debug('This should not happen (_on_new_account)')
sb's avatar
sb committed

	def account_changed(self, new_name):
		self.name = new_name

	def request_last_status_time(self, jid, resource):
		gajim.log.debug('This should not happen (request_last_status_time)')
sb's avatar
sb committed
	def request_os_info(self, jid, resource):
		gajim.log.debug('This should not happen (request_os_info)')
sb's avatar
sb committed

	def get_settings(self):
		gajim.log.debug('This should not happen (get_settings)')
sb's avatar
sb committed

	def get_bookmarks(self):
		gajim.log.debug('This should not happen (get_bookmarks)')
sb's avatar
sb committed
	def store_bookmarks(self):
		gajim.log.debug('This should not happen (store_bookmarks)')
sb's avatar
sb committed
	def get_metacontacts(self):
		gajim.log.debug('This should not happen (get_metacontacts)')
sb's avatar
sb committed
	def send_agent_status(self, agent, ptype):
		gajim.log.debug('This should not happen (send_agent_status)')
sb's avatar
sb committed

	def gpg_passphrase(self, passphrase):
		if USE_GPG:
			use_gpg_agent = gajim.config.get('use_gpg_agent')
			if use_gpg_agent:
				self.gpg.passphrase = None
			else:
				self.gpg.passphrase = passphrase

	def ask_gpg_keys(self):
		if USE_GPG:
			keys = self.gpg.get_keys()
			return keys
		return None

	def ask_gpg_secrete_keys(self):
		if USE_GPG:
			keys = self.gpg.get_secret_keys()
			return keys
		return None

	def _event_dispatcher(self, realm, event, data):
		if realm == '':
			if event == common.xmpp.transports.DATA_RECEIVED:
				self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
			elif event == common.xmpp.transports.DATA_SENT:
				self.dispatch('STANZA_SENT', unicode(data))

# END ConnectionZeroconf