gajim-plural/src/common/zeroconf/connection_zeroconf.py

565 lines
19 KiB
Python
Raw Normal View History

## common/zeroconf/connection_zeroconf.py
2006-05-26 16:27:42 +02:00
##
## 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>
2006-05-26 16:27:42 +02:00
##
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
## Vincent Hanquez <tab@snarc.org>
## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
2006-05-26 16:27:42 +02:00
## 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>
2006-05-26 16:27:42 +02:00
##
## 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
import gobject
import notify
2006-05-26 16:27:42 +02:00
from common import helpers
from common import gajim
from common import GnuPG
2006-06-28 01:09:21 +02:00
from common.zeroconf import zeroconf
from common.zeroconf import connection_handlers_zeroconf
from common.zeroconf import client_zeroconf
2006-05-26 16:27:42 +02:00
from connection_handlers_zeroconf import *
2006-05-26 16:27:42 +02:00
USE_GPG = GnuPG.USE_GPG
class ConnectionZeroconf(ConnectionHandlersZeroconf):
'''Connection class'''
def __init__(self, name):
ConnectionHandlersZeroconf.__init__(self)
# system username
self.username = None
2006-05-26 16:27:42 +02:00
self.name = name
self.connected = 0 # offline
self.connection = None
2006-05-26 16:27:42 +02:00
self.gpg = None
2006-05-29 21:57:39 +02:00
self.is_zeroconf = True
2006-05-26 16:27:42 +02:00
self.status = ''
self.old_show = ''
self.call_resolve_timeout = False
#self.time_to_reconnect = None
#self.new_account_info = None
self.bookmarks = []
#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
2006-05-26 16:27:42 +02:00
# 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.zeroconf = zeroconf.Zeroconf(self._on_new_service,
self._on_remove_service, self._on_name_conflictCB,
self._on_disconnected, self.username, self.host, self.port)
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)
else:
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')
2006-05-26 16:27:42 +02:00
# END __init__
2006-05-26 16:27:42 +02:00
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')
2006-05-29 21:57:39 +02:00
signed = self.get_signed_msg(self.status)
2006-05-26 16:27:42 +02:00
def quit(self, kill_core):
2006-05-26 16:27:42 +02:00
if kill_core and self.connected > 1:
2006-09-18 00:19:10 +02:00
self.disconnect()
2006-05-26 16:27:42 +02:00
2006-09-18 00:19:10 +02:00
def disable_account(self):
self.disconnect()
2006-05-26 16:27:42 +02:00
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
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))
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):
self.roster.delItem(jid)
# 'NOTIFY' (account, (jid, status, status message, resource, priority,
# keyID, timestamp))
self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0))
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'
def connect(self, data = None, show = 'online', msg = ''):
self.get_config_values_or_default()
self.zeroconf.txt['status'] = show
self.zeroconf.txt['msg'] = msg
self.zeroconf.txt['1st'] = self.first
self.zeroconf.txt['last'] = self.last
self.zeroconf.txt['jid'] = self.jabber_id
self.zeroconf.txt['email'] = self.email
self.zeroconf.username = self.username
self.zeroconf.host = self.host
self.zeroconf.port = self.port
2006-05-29 21:57:39 +02:00
if self.connection:
return self.connection, ''
if self.zeroconf.connect():
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)
else:
self.dispatch('STATUS', 'offline')
self.status = 'offline'
def disconnect(self, on_purpose = False):
self.connected = 0
self.time_to_reconnect = None
if self.connection:
2006-09-19 10:58:14 +02:00
if self.connection.listener:
self.connection.listener.disconnect()
self.connection = None
# stop calling the timeout
self.call_resolve_timeout = False
self.zeroconf.disconnect()
def reconnect(self, new_port, use_tls):
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')
txt2 = {}
for key, val in txt.iteritems():
if val != '':
txt2[key] = val
port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
if new_port or use_tls:
self.connection.kill_all_connections()
self.connection.listener.disconnect()
self.connection.start_listener(port)
self.zeroconf.remove_announce()
self.zeroconf.txt = txt2
self.zeroconf.port = port
self.zeroconf.announce()
2006-05-26 16:27:42 +02:00
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
2006-05-29 21:57:39 +02:00
# 'connect'
2006-05-26 16:27:42 +02:00
if show != 'offline' and not self.connected:
self.connect(None, show, msg)
if show != 'invisible':
check = self.zeroconf.announce()
else:
self.connected = STATUS_LIST.index(show)
2006-05-26 16:27:42 +02:00
2006-05-29 21:57:39 +02:00
# 'disconnect'
2006-05-26 16:27:42 +02:00
elif show == 'offline' and self.connected:
2006-05-29 21:57:39 +02:00
self.disconnect()
self.dispatch('STATUS', 'offline')
# update status
2006-05-26 16:27:42 +02:00
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':
txt = {}
txt['status'] = show
txt['msg'] = msg
check = check and self.zeroconf.update_txt(txt)
#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.')))
2006-05-26 16:27:42 +02:00
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):
fjid = jid
2006-05-26 16:27:42 +02:00
if not self.connection:
return
if not msg and chatstate is None:
return
2006-05-26 16:27:42 +02:00
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
2006-05-26 16:27:42 +02:00
if type == 'chat':
msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
2006-05-26 16:27:42 +02:00
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')
2006-05-26 16:27:42 +02:00
if msgenc:
msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
2006-05-26 16:27:42 +02:00
# 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)
2006-05-26 16:27:42 +02:00
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)
2006-05-26 16:27:42 +02:00
self.dispatch('MSGSENT', (jid, msg, keyID))
2006-05-26 16:27:42 +02:00
def send_stanza(self, stanza):
# send a stanza untouched
print 'connection_zeroconf.py: send_stanza'
2006-05-26 16:27:42 +02:00
if not self.connection:
return
#self.connection.send(stanza)
pass
2006-05-26 16:27:42 +02:00
def ack_subscribed(self, jid):
gajim.log.debug('This should not happen (ack_subscribed)')
2006-05-26 16:27:42 +02:00
def ack_unsubscribed(self, jid):
gajim.log.debug('This should not happen (ack_unsubscribed)')
2006-05-26 16:27:42 +02:00
def request_subscription(self, jid, msg = '', name = '', groups = [],
auto_auth = False):
gajim.log.debug('This should not happen (request_subscription)')
2006-05-26 16:27:42 +02:00
def send_authorization(self, jid):
gajim.log.debug('This should not happen (send_authorization)')
2006-05-26 16:27:42 +02:00
def refuse_authorization(self, jid):
gajim.log.debug('This should not happen (refuse_authorization)')
2006-05-26 16:27:42 +02:00
def unsubscribe(self, jid, remove_auth = True):
gajim.log.debug('This should not happen (unsubscribe)')
2006-05-26 16:27:42 +02:00
def unsubscribe_agent(self, agent):
gajim.log.debug('This should not happen (unsubscribe_agent)')
2006-05-26 16:27:42 +02:00
def update_contact(self, jid, name, groups):
2006-05-26 16:27:42 +02:00
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)')
2006-05-26 16:27:42 +02:00
def _on_new_account(self, con = None, con_type = None):
gajim.log.debug('This should not happen (_on_new_account)')
2006-05-26 16:27:42 +02:00
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)')
2006-05-26 16:27:42 +02:00
def request_os_info(self, jid, resource):
'''
2006-05-26 16:27:42 +02:00
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)
'''
pass
2006-05-26 16:27:42 +02:00
def get_settings(self):
gajim.log.debug('This should not happen (get_settings)')
2006-05-26 16:27:42 +02:00
def get_bookmarks(self):
gajim.log.debug('This should not happen (get_bookmarks)')
2006-05-26 16:27:42 +02:00
def store_bookmarks(self):
gajim.log.debug('This should not happen (store_bookmarks)')
2006-07-11 20:17:25 +02:00
2006-05-26 16:27:42 +02:00
def get_metacontacts(self):
gajim.log.debug('This should not happen (get_metacontacts)')
2006-07-11 20:17:25 +02:00
2006-05-26 16:27:42 +02:00
def send_agent_status(self, agent, ptype):
gajim.log.debug('This should not happen (send_agent_status)')
2006-05-26 16:27:42 +02:00
def join_gc(self, nick, room, server, password):
gajim.log.debug('This should not happen (join_gc)')
2006-05-26 16:27:42 +02:00
def send_gc_message(self, jid, msg):
gajim.log.debug('This should not happen (send_gc_message)')
2006-05-26 16:27:42 +02:00
def send_gc_subject(self, jid, subject):
gajim.log.debug('This should not happen (send_gc_subject)')
2006-05-26 16:27:42 +02:00
def request_gc_config(self, room_jid):
gajim.log.debug('This should not happen (request_gc_config)')
2006-07-11 20:17:25 +02:00
2006-05-26 16:27:42 +02:00
def change_gc_nick(self, room_jid, nick):
gajim.log.debug('This should not happen (change_gc_nick)')
2006-05-26 16:27:42 +02:00
def send_gc_status(self, nick, jid, show, status):
gajim.log.debug('This should not happen (send_gc_status)')
2006-05-26 16:27:42 +02:00
def gc_set_role(self, room_jid, nick, role, reason = ''):
gajim.log.debug('This should not happen (gc_set_role)')
2006-05-26 16:27:42 +02:00
def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
gajim.log.debug('This should not happen (gc_set_affiliation)')
2006-05-26 16:27:42 +02:00
def send_gc_affiliation_list(self, room_jid, list):
gajim.log.debug('This should not happen (send_gc_affiliation_list)')
2006-05-26 16:27:42 +02:00
def get_affiliation_list(self, room_jid, affiliation):
gajim.log.debug('This should not happen (get_affiliation_list)')
2006-05-26 16:27:42 +02:00
def send_gc_config(self, room_jid, config):
gajim.log.debug('This should not happen (send_gc_config)')
2006-05-26 16:27:42 +02:00
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
'''
2006-05-26 16:27:42 +02:00
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)
'''
pass
2006-05-26 16:27:42 +02:00
def unregister_account(self, on_remove_success):
gajim.log.debug('This should not happen (unregister_account)')
2006-05-26 16:27:42 +02:00
def send_invite(self, room, to, reason=''):
gajim.log.debug('This should not happen (send_invite)')
2006-05-26 16:27:42 +02:00
def send_keepalive(self):
# nothing received for the last foo seconds (60 secs by default)
pass
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))
2006-05-29 21:57:39 +02:00
# END ConnectionZeroconf