Skip to content
Snippets Groups Projects
connection_zeroconf.py 16.6 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 zeroconf
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)
		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
sb's avatar
sb committed
		self.status = ''
		self.old_show = ''
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'
		#XXX use that somewhere
		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.on_connect_success = None
		self.on_connect_failure = None
		self.retrycount = 0
		self.jids_for_auto_auth = [] # list of jid to auto-authorize
		self.get_config_values_or_default()
		self.muc_jid = {} # jid of muc server for each transport type
		self.vcard_supported = False

	def get_config_values_or_default(self):
		''' get name, host, port from config, or 
		create zeroconf account with default values'''
		if not gajim.config.get_per('accounts', 'zeroconf', 'name'):
			print 'Creating zeroconf account'
			gajim.config.add_per('accounts', 'zeroconf')
			gajim.config.set_per('accounts', 'zeroconf', 'autoconnect', True)
			gajim.config.set_per('accounts', 'zeroconf', 'no_log_for', '')
			gajim.config.set_per('accounts', 'zeroconf', 'password', 'zeroconf')
			gajim.config.set_per('accounts', 'zeroconf', 'sync_with_global_status', True)
			username = unicode(getpass.getuser())
			gajim.config.set_per('accounts', 'zeroconf', 'name', username)
			#XXX make sure host is US-ASCII
			host = unicode(socket.gethostname())
			gajim.config.set_per('accounts', 'zeroconf', 'hostname', host)
			port = 5298
			gajim.config.set_per('accounts', 'zeroconf', 'custom_port', port)
			gajim.config.set_per('accounts', 'zeroconf', 'is_zeroconf', True)
		else:
			username = gajim.config.get_per('accounts', 'zeroconf', 'name')
			host = gajim.config.get_per('accounts', 'zeroconf', 'hostname')
			port = gajim.config.get_per('accounts', 'zeroconf', 'custom_port')
			self.autoconnect = gajim.config.get_per('accounts', 'zeroconf', 'autoconnect')
			self.sync_with_global_status = gajim.config.get_per('accounts', 'zeroconf', 'sync_with_global_status')
			self.no_log_for = gajim.config.get_per('accounts', 'zeroconf', 'no_log_for')

		self.zeroconf = zeroconf.Zeroconf(self._on_new_service, self._on_remove_service, username, host, port)
sb's avatar
sb committed

sb's avatar
sb committed
	# END __init__
	def put_event(self, ev):
		if gajim.handlers.has_key(ev[0]):
			gajim.handlers[ev[0]](self.name, ev[1])

	def dispatch(self, event, data):
		'''always passes account name as first param'''
		self.put_event((event, data))

	def _reconnect(self):
		gajim.log.debug('reconnect')
		signed = self.get_signed_msg(self.status)
sb's avatar
sb committed
			
	def quit(self, kill_core):
sb's avatar
sb committed
		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:
			self.zeroconf.resolve_all()
			diffs = self.roster.getDiffs()
			for key in diffs:
				self.roster.setItem(key)
				self.dispatch('NOTIFY', (key, self.roster.getStatus(key), self.roster.getMessage(key), 'local', 0, None, 0))
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))
sb's avatar
sb committed
		
	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 connect(self, data = None, show = 'online'):
		if self.connection:
			return self.connection, ''
sb's avatar
sb committed
		
			self.connection = client_zeroconf.ClientZeroconf(self.zeroconf, self)
			self.roster = self.connection.getRoster()
			self.dispatch('ROSTER', self.roster)

			#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))

			self.connected = STATUS_LIST.index(show)

			# refresh all contacts data every second
			self.call_resolve_timeout = True
			gobject.timeout_add(10000, self._on_resolve_timeout)
			#TODO: display visual notification that we could not connect to avahi
sb's avatar
sb committed
	def connect_and_init(self, show, msg, signed):
		self.continue_connect_info = [show, msg, signed]
		self.zeroconf.txt['status'] = show
		self.zeroconf.txt['msg'] = msg
sb's avatar
sb committed


	def disconnect(self, on_purpose = False):
		self.connected = 0
		self.time_to_reconnect = None
		if self.connection:
dkirov's avatar
dkirov committed
			if self.connection.listener:
				self.connection.listener.disconnect()
			self.connection = None
			# stop calling the timeout
			self.call_resolve_timeout = False
			self.zeroconf.disconnect()

sb's avatar
sb committed
	def change_status(self, show, msg, sync = False, auto = False):
		if not show in STATUS_LIST:
			return -1
		check = True		#to check for errors from zeroconf

		# 'connect'
sb's avatar
sb committed
		if show != 'offline' and not self.connected:
			self.connect_and_init(show, msg, '')
			else:
					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':
				check = check and self.zeroconf.remove_announce()
			elif was_invisible:
				check = check and self.zeroconf.announce()
			if self.connection and not show == 'invisible':
				check = check and self.zeroconf.update_txt(txt)

		#stay offline when zeroconf does something wrong
		if check:
			self.dispatch('STATUS', show)
		else:
#			self.dispatch('ERROR', 'Could not change status. Please check if avahi-daemon is running.')
			notify.popup(_('Connection problem:'), 'zeroconf', None,
					title=_('Could not change status'),
					text=_('Please check if avahi-daemon is running.') ) 
			self.dispatch('STATUS', 'offline')
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)
		#~ self.zeroconf.send_message(jid, msgtxt, type)
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):
sb's avatar
sb committed
		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_VERSION)
		self.connection.send(iq)
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 join_gc(self, nick, room, server, password):
		gajim.log.debug('This should not happen (join_gc)')
sb's avatar
sb committed
	def send_gc_message(self, jid, msg):
		gajim.log.debug('This should not happen (send_gc_message)')
sb's avatar
sb committed
	def send_gc_subject(self, jid, subject):
		gajim.log.debug('This should not happen (send_gc_subject)')
sb's avatar
sb committed
	def request_gc_config(self, room_jid):
		gajim.log.debug('This should not happen (request_gc_config)')
sb's avatar
sb committed
	def change_gc_nick(self, room_jid, nick):
		gajim.log.debug('This should not happen (change_gc_nick)')
sb's avatar
sb committed
	def send_gc_status(self, nick, jid, show, status):
		gajim.log.debug('This should not happen (send_gc_status)')
sb's avatar
sb committed
	def gc_set_role(self, room_jid, nick, role, reason = ''):
		gajim.log.debug('This should not happen (gc_set_role)')
sb's avatar
sb committed
	def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
		gajim.log.debug('This should not happen (gc_set_affiliation)')
sb's avatar
sb committed
	def send_gc_affiliation_list(self, room_jid, list):
		gajim.log.debug('This should not happen (send_gc_affiliation_list)')
sb's avatar
sb committed
	def get_affiliation_list(self, room_jid, affiliation):
		gajim.log.debug('This should not happen (get_affiliation_list)')
sb's avatar
sb committed
	def send_gc_config(self, room_jid, config):
		gajim.log.debug('This should not happen (send_gc_config)')
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 change_password(self, password):
		if not self.connection:
			return
sb's avatar
sb committed
		hostname = gajim.config.get_per('accounts', self.name, 'hostname')
		username = gajim.config.get_per('accounts', self.name, 'name')
		iq = common.xmpp.Iq(typ = 'set', to = hostname)
		q = iq.setTag(common.xmpp.NS_REGISTER + ' query')
		q.setTagData('username',username)
		q.setTagData('password',password)
		self.connection.send(iq)
sb's avatar
sb committed
	def unregister_account(self, on_remove_success):
		gajim.log.debug('This should not happen (unregister_account)')
sb's avatar
sb committed
	def send_invite(self, room, to, reason=''):
		gajim.log.debug('This should not happen (send_invite)')
sb's avatar
sb committed
	def send_keepalive(self):
		# nothing received for the last foo seconds (60 secs by default)
	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