diff --git a/data/glade/manage_proxies_window.glade b/data/glade/manage_proxies_window.glade
index 758cfcce7..9580b12e3 100644
--- a/data/glade/manage_proxies_window.glade
+++ b/data/glade/manage_proxies_window.glade
@@ -211,7 +211,8 @@
True
HTTP Connect
-SOCKS5
+SOCKS5
+BOSH
False
True
@@ -234,6 +235,7 @@ SOCKS5
0
True
+ ●
False
@@ -304,7 +306,7 @@ SOCKS5
True
False
- 5
+ 8
2
False
6
@@ -313,7 +315,7 @@ SOCKS5
True
- _Port:
+ Proxy _Port:
True
False
GTK_JUSTIFY_LEFT
@@ -332,8 +334,8 @@ SOCKS5
0
1
- 1
- 2
+ 4
+ 5
fill
@@ -348,14 +350,15 @@ SOCKS5
0
True
+ ●
False
1
2
- 0
- 1
+ 3
+ 4
@@ -369,14 +372,15 @@ SOCKS5
0
True
+ ●
False
1
2
- 1
- 2
+ 4
+ 5
@@ -384,7 +388,7 @@ SOCKS5
True
- _Host:
+ Proxy _Host:
True
False
GTK_JUSTIFY_LEFT
@@ -403,8 +407,8 @@ SOCKS5
0
1
- 0
- 1
+ 3
+ 4
fill
@@ -432,8 +436,8 @@ SOCKS5
0
1
- 4
- 5
+ 7
+ 8
fill
@@ -461,8 +465,8 @@ SOCKS5
0
1
- 3
- 4
+ 6
+ 7
fill
@@ -477,14 +481,15 @@ SOCKS5
0
True
+ ●
False
1
2
- 4
- 5
+ 7
+ 8
@@ -498,14 +503,15 @@ SOCKS5
0
True
+ ●
False
1
2
- 3
- 4
+ 6
+ 7
@@ -514,7 +520,7 @@ SOCKS5
True
True
- Use authentication
+ Use proxy authentication
True
GTK_RELIEF_NORMAL
True
@@ -523,6 +529,80 @@ SOCKS5
True
+
+ 0
+ 2
+ 5
+ 6
+ fill
+
+
+
+
+
+
+ True
+ _BOSH URL:
+ True
+ False
+ GTK_JUSTIFY_LEFT
+ False
+ False
+ 0
+ 0.5
+ 0
+ 0
+ proxyhost_entry
+ PANGO_ELLIPSIZE_NONE
+ -1
+ False
+ 0
+
+
+ 0
+ 1
+ 0
+ 1
+ fill
+
+
+
+
+
+
+ True
+ True
+ True
+ True
+ 0
+
+ True
+ ●
+ False
+
+
+
+ 1
+ 2
+ 0
+ 1
+
+
+
+
+
+
+ True
+ True
+ Use HTTP proxy
+ True
+ GTK_RELIEF_NORMAL
+ True
+ False
+ False
+ True
+
+
0
2
@@ -532,6 +612,57 @@ SOCKS5
+
+
+
+ True
+ B_OSH Port:
+ True
+ False
+ GTK_JUSTIFY_LEFT
+ False
+ False
+ 0
+ 0.5
+ 0
+ 0
+ proxyhost_entry
+ PANGO_ELLIPSIZE_NONE
+ -1
+ False
+ 0
+
+
+ 0
+ 1
+ 1
+ 2
+ fill
+
+
+
+
+
+
+ True
+ True
+ True
+ True
+ 0
+
+ True
+ ●
+ False
+
+
+
+ 1
+ 2
+ 1
+ 2
+
+
+
diff --git a/src/common/config.py b/src/common/config.py
index 33447822a..0e577f4fc 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -358,8 +358,17 @@ class Config:
'type': [ opt_str, 'http' ],
'host': [ opt_str, '' ],
'port': [ opt_int, 3128 ],
+ 'useauth': [ opt_bool, False ],
'user': [ opt_str, '' ],
'pass': [ opt_str, '' ],
+ 'bosh_uri': [ opt_str, '' ],
+ 'bosh_port': [ opt_int, 80 ],
+ 'bosh_useproxy': [ opt_bool, False ],
+ 'bosh_wait': [ opt_int, 30 ],
+ 'bosh_hold': [ opt_int, 2 ],
+ 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ],
+ 'bosh_http_pipelining': [ opt_bool, False ],
+ 'bosh_wait_for_restart_response': [ opt_bool, False ],
}, {}),
'themes': ({
'accounttextcolor': [ opt_color, 'black', '', True ],
diff --git a/src/common/connection.py b/src/common/connection.py
index fa68745dc..c137a2809 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -219,7 +219,7 @@ class Connection(ConnectionHandlers):
def _disconnectedReconnCB(self):
'''Called when we are disconnected'''
- log.debug('disconnectedReconnCB')
+ log.info('disconnectedReconnCB called')
if gajim.account_is_connected(self.name):
# we cannot change our status to offline or connecting
# after we auth to server
@@ -374,9 +374,9 @@ class Connection(ConnectionHandlers):
# data is (dict)
self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
elif realm == '':
- if event == common.xmpp.transports.DATA_RECEIVED:
+ if event == common.xmpp.transports_nb.DATA_RECEIVED:
self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
- elif event == common.xmpp.transports.DATA_SENT:
+ elif event == common.xmpp.transports_nb.DATA_SENT:
self.dispatch('STANZA_SENT', unicode(data))
def _select_next_host(self, hosts):
@@ -407,7 +407,7 @@ class Connection(ConnectionHandlers):
def connect(self, data = None):
''' Start a connection to the Jabber server.
- Returns connection, and connection type ('tls', 'ssl', 'tcp', '')
+ Returns connection, and connection type ('tls', 'ssl', 'plain', '')
data MUST contain hostname, usessl, proxy, use_custom_host,
custom_host (if use_custom_host), custom_port (if use_custom_host)'''
if self.connection:
@@ -437,11 +437,11 @@ class Connection(ConnectionHandlers):
# create connection if it doesn't already exist
self.connected = 1
if p and p in gajim.config.get_per('proxies'):
- 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')
- proxy['type'] = gajim.config.get_per('proxies', p, 'type')
+ proxy = {}
+ proxyptr = gajim.config.get_per('proxies', p)
+ for key in proxyptr.keys():
+ proxy[key] = proxyptr[key][1]
+
elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
try:
try:
@@ -477,7 +477,6 @@ class Connection(ConnectionHandlers):
proxy = None
else:
proxy = None
-
h = hostname
p = 5222
ssl_p = 5223
@@ -513,67 +512,13 @@ class Connection(ConnectionHandlers):
i['ssl_port'] = ssl_p
self._connect_to_next_host()
- def on_proxy_failure(self, reason):
- log.debug('Connection to proxy failed')
- self.time_to_reconnect = None
- self.on_connect_failure = None
- self.disconnect(on_purpose = True)
- self.dispatch('STATUS', 'offline')
- self.dispatch('CONNECTION_LOST',
- (_('Connection to proxy failed'), reason))
-
- def _connect_to_next_type(self, retry=False):
- log.debug('Connection to next type')
- if len(self._connection_types):
- self._current_type = self._connection_types.pop(0)
- if self.last_connection:
- self.last_connection.socket.disconnect()
- self.last_connection = None
- self.connection = None
- if gajim.verbose:
- if log.getEffectiveLevel() == logging.DEBUG:
- d = ['always']
- else:
- d = ['always', 'nodebuilder']
- con = common.xmpp.NonBlockingClient(self._hostname, debug = d,
- caller = self, on_connect = self.on_connect_success,
- on_proxy_failure = self.on_proxy_failure,
- on_connect_failure = self._connect_to_next_type)
- else:
- con = common.xmpp.NonBlockingClient(self._hostname, debug = [],
- caller = self, on_connect = self.on_connect_success,
- on_proxy_failure = self.on_proxy_failure,
- on_connect_failure = self._connect_to_next_type)
- self.last_connection = con
- # increase default timeout for server responses
- common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
- con.set_idlequeue(gajim.idlequeue)
- # FIXME: this is a hack; need a better way
- if self.on_connect_success == self._on_new_account:
- con.RegisterDisconnectHandler(self._on_new_account)
-
- if self._current_type == 'ssl':
- port = self._current_host['ssl_port']
- secur = 1
- else:
- port = self._current_host['port']
- if self._current_type == 'plain':
- secur = 0
- else:
- secur = None
- log.info('Connecting to %s: [%s:%d]', self.name,
- self._current_host['host'], port)
- con.connect((self._current_host['host'], port), proxy=self._proxy,
- secure = secur)
- else:
- self._connect_to_next_host(retry)
def _connect_to_next_host(self, retry = False):
log.debug('Connection to next host')
if len(self._hosts):
# No config option exist when creating a new account
if self.last_connection_type:
- if self.last_connection_type == 'tcp':
+ if self.last_connection_type == 'plain':
self._connection_types = ['plain']
else:
self._connection_types = [self.last_connection_type]
@@ -582,10 +527,20 @@ class Connection(ConnectionHandlers):
'connection_types').split()
else:
self._connection_types = ['tls', 'ssl', 'plain']
+
+ if self._proxy and self._proxy['type']=='bosh':
+ # with BOSH, we can't do TLS negotiation with , we do only "plain"
+ # connection and TLS with handshake right after TCP connecting ("ssl")
+ scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
+ if scheme=='https':
+ self._connection_types = ['ssl']
+ else:
+ self._connection_types = ['plain']
+
host = self._select_next_host(self._hosts)
self._current_host = host
self._hosts.remove(host)
- self._connect_to_next_type()
+ self.connect_to_next_type()
else:
if not retry and self.retrycount == 0:
@@ -601,6 +556,69 @@ class Connection(ConnectionHandlers):
# try reconnect if connection has failed before auth to server
self._disconnectedReconnCB()
+ def connect_to_next_type(self, retry=False):
+ if len(self._connection_types):
+ self._current_type = self._connection_types.pop(0)
+ if self.last_connection:
+ self.last_connection.socket.disconnect()
+ self.last_connection = None
+ self.connection = None
+
+ if self._current_type == 'ssl':
+ # SSL (force TLS on different port than plain)
+ # If we do TLS over BOSH, port of XMPP server should be the standard one
+ # and TLS should be negotiated because TLS on 5223 is deprecated
+ if self._proxy and self._proxy['type']=='bosh':
+ port = self._current_host['port']
+ else:
+ port = self._current_host['ssl_port']
+ elif self._current_type == 'tls':
+ # TLS - negotiate tls after XMPP stream is estabilished
+ port = self._current_host['port']
+ elif self._current_type == 'plain':
+ # plain connection on defined port
+ port = self._current_host['port']
+
+ cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
+ mycerts = common.gajim.MY_CACERTS
+ secure_tuple = (self._current_type, cacerts, mycerts)
+
+ con = common.xmpp.NonBlockingClient(
+ domain=self._hostname,
+ caller=self,
+ idlequeue=gajim.idlequeue)
+
+ self.last_connection = con
+ # increase default timeout for server responses
+ common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
+ # FIXME: this is a hack; need a better way
+ if self.on_connect_success == self._on_new_account:
+ con.RegisterDisconnectHandler(self._on_new_account)
+
+ self.log_hosttype_info(port)
+ con.connect(
+ hostname=self._current_host['host'],
+ port=port,
+ on_connect=self.on_connect_success,
+ on_proxy_failure=self.on_proxy_failure,
+ on_connect_failure=self.connect_to_next_type,
+ proxy=self._proxy,
+ secure_tuple = secure_tuple)
+ else:
+ self._connect_to_next_host(retry)
+
+ def log_hosttype_info(self, port):
+ msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
+ self._current_host['host'], port, self._current_type)
+ log.info(msg)
+ if self._proxy:
+ msg = '>>>>>> '
+ if self._proxy['type']=='bosh':
+ msg = '%s over BOSH %s:%s' % (msg, self._proxy['bosh_uri'], self._proxy['bosh_port'])
+ if self._proxy['type'] in ['http','socks5'] or self._proxy['bosh_useproxy']:
+ msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port'])
+ log.info(msg)
+
def _connect_failure(self, con_type = None):
if not con_type:
# we are not retrying, and not conecting
@@ -611,16 +629,24 @@ class Connection(ConnectionHandlers):
(_('Could not connect to "%s"') % self._hostname,
_('Check your connection or try again later.')))
+ def on_proxy_failure(self, reason):
+ log.error('Connection to proxy failed: %s' % reason)
+ self.time_to_reconnect = None
+ self.on_connect_failure = None
+ self.disconnect(on_purpose = True)
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('CONNECTION_LOST',
+ (_('Connection to proxy failed'), reason))
+
def _connect_success(self, con, con_type):
if not self.connected: # We went offline during connecting process
# FIXME - not possible, maybe it was when we used threads
return
_con_type = con_type
- # xmpp returns 'tcp', but we set 'plain' in connection_types in config
- if _con_type == 'tcp':
- _con_type = 'plain'
if _con_type != self._current_type:
- self._connect_to_next_type()
+ log.info('Connecting to next type beacuse desired is %s and returned is %s'
+ % (self._current_type, _con_type))
+ self.connect_to_next_type()
return
con.RegisterDisconnectHandler(self._on_disconnected)
if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
@@ -682,7 +708,12 @@ class Connection(ConnectionHandlers):
(con.Connection.ssl_fingerprint_sha1,))
return True
self._register_handlers(con, con_type)
- con.auth(name, self.password, self.server_resource, 1, self.__on_auth)
+ con.auth(
+ user=name,
+ password=self.password,
+ resource=self.server_resource,
+ sasl=1,
+ on_auth=self.__on_auth)
def ssl_certificate_accepted(self):
if not self.connection:
@@ -1019,7 +1050,11 @@ class Connection(ConnectionHandlers):
p.setStatus(msg)
self.remove_all_transfers()
self.time_to_reconnect = None
- self.connection.start_disconnect(p, self._on_disconnected)
+
+ self.connection.RegisterDisconnectHandler(self._on_disconnected)
+ self.connection.send(p, now=True)
+ self.connection.start_disconnect()
+ #self.connection.start_disconnect(p, self._on_disconnected)
else:
self.time_to_reconnect = None
self._on_disconnected()
@@ -1054,7 +1089,7 @@ class Connection(ConnectionHandlers):
def _on_disconnected(self):
''' called when a disconnect request has completed successfully'''
self.dispatch('STATUS', 'offline')
- self.disconnect()
+ self.disconnect(on_purpose=True)
def get_status(self):
return STATUS_LIST[self.connected]
@@ -1072,7 +1107,7 @@ class Connection(ConnectionHandlers):
chatstate=None, msg_id=None, composing_xep=None, resource=None,
user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
original_message=None, delayed=None):
- if not self.connection:
+ if not self.connection or self.connected < 2:
return 1
try:
jid = helpers.parse_jid(jid)
@@ -1202,6 +1237,7 @@ class Connection(ConnectionHandlers):
if session.enable_encryption:
msg_iq = session.encrypt_stanza(msg_iq)
+ print msg_iq
msg_id = self.connection.send(msg_iq)
if not forward_from and session and session.is_loggable():
ji = gajim.get_jid_without_resource(jid)
@@ -1613,7 +1649,7 @@ class Connection(ConnectionHandlers):
def gc_got_disconnected(self, room_jid):
''' A groupchat got disconnected. This can be or purpose or not.
Save the time we quit to avoid duplicate logs AND be faster than get that
- date from DB. Save it in mem AND in a small table (with fast access)
+ date from DB. Save it in mem AND in a small table (with fast access)
'''
log_time = time_time()
self.last_history_time[room_jid] = log_time
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 4e5c21b0d..fa5896a64 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -214,7 +214,7 @@ class ConnectionBytestream:
iq.setID(file_props['request-id'])
query = iq.setTag('query')
query.setNamespace(common.xmpp.NS_BYTESTREAM)
- query.setAttr('mode', 'tcp')
+ query.setAttr('mode', 'plain')
query.setAttr('sid', file_props['sid'])
for ft_host in ft_add_hosts:
# The streamhost, if set
@@ -1294,7 +1294,7 @@ class ConnectionHandlersBase:
except KeyError:
return None
- def terminate_sessions(self, send_termination = False):
+ def terminate_sessions(self, send_termination=False):
'''send termination messages and delete all active sessions'''
for jid in self.sessions:
for thread_id in self.sessions[jid]:
@@ -2011,7 +2011,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
status = prs.getStatus() or ''
show = prs.getShow()
- if not show in ['away', 'chat', 'dnd', 'xa']:
+ if not show in STATUS_LIST:
show = '' # We ignore unknown show
if not ptype and not show:
show = 'online'
diff --git a/src/common/nslookup.py b/src/common/resolver.py
similarity index 63%
rename from src/common/nslookup.py
rename to src/common/resolver.py
index 943eca1ed..52c297ff7 100644
--- a/src/common/nslookup.py
+++ b/src/common/resolver.py
@@ -1,10 +1,6 @@
-# -*- coding:utf-8 -*-
-## src/common/nslookup.py
+## common/resolver.py
##
-## Copyright (C) 2006 Dimitur Kirov
-## Nikos Kouremenos
-## Copyright (C) 2007 Yann Leboulanger
-## Copyright (C) 2008 Jonathan Schleifer
+## Copyright (C) 2006 Dimitur Kirov
##
## This file is part of Gajim.
##
@@ -14,20 +10,20 @@
##
## Gajim 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
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see .
+## along with Gajim. If not, see .
##
import sys
import os
import re
-from common import helpers
from xmpp.idlequeue import *
+# needed for nslookup
if os.name == 'nt':
from subprocess import * # python24 only. we ask this for Windows
elif os.name == 'posix':
@@ -39,26 +35,151 @@ ns_type_pattern = re.compile('^[a-z]+$')
# match srv host_name
host_pattern = re.compile('^[a-z0-9\-._]*[a-z0-9]\.[a-z]{2,}$')
-class Resolver:
- def __init__(self, idlequeue):
- self.idlequeue = idlequeue
- # dict {host : list of srv records}
+USE_LIBASYNCNS = False
+
+try:
+ #raise ImportError("Manually disabled libasync")
+ import libasyncns
+ USE_LIBASYNCNS = True
+ log.info("libasyncns-python loaded")
+except ImportError:
+ log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True)
+
+ # FIXME: Remove these prints before release, replace with a warning dialog.
+ print >> sys.stderr, "=" * 79
+ print >> sys.stderr, "libasyncns-python not installed which means:"
+ print >> sys.stderr, " - nslookup will be used for SRV and TXT requests"
+ print >> sys.stderr, " - getaddrinfo will block"
+ print >> sys.stderr, "libasyncns-python can be found at https://launchpad.net/libasyncns-python"
+ print >> sys.stderr, "=" * 79
+
+
+def get_resolver(idlequeue):
+ if USE_LIBASYNCNS:
+ return LibAsyncNSResolver()
+ else:
+ return NSLookupResolver(idlequeue)
+
+class CommonResolver():
+ def __init__(self):
+ # dict {"host+type" : list of records}
self.resolved_hosts = {}
- # dict {host : list of callbacks}
+ # dict {"host+type" : list of callbacks}
self.handlers = {}
+ def resolve(self, host, on_ready, type='srv'):
+ assert(type in ['srv', 'txt'])
+ if not host:
+ # empty host, return empty list of srv records
+ on_ready([])
+ return
+ if self.resolved_hosts.has_key(host+type):
+ # host is already resolved, return cached values
+ on_ready(host, self.resolved_hosts[host+type])
+ return
+ if self.handlers.has_key(host+type):
+ # host is about to be resolved by another connection,
+ # attach our callback
+ self.handlers[host+type].append(on_ready)
+ else:
+ # host has never been resolved, start now
+ self.handlers[host+type] = [on_ready]
+ self.start_resolve(host, type)
+
+ def _on_ready(self, host, type, result_list):
+ # practically it is impossible to be the opposite, but who knows :)
+ if not self.resolved_hosts.has_key(host+type):
+ self.resolved_hosts[host+type] = result_list
+ if self.handlers.has_key(host+type):
+ for callback in self.handlers[host+type]:
+ callback(host, result_list)
+ del(self.handlers[host+type])
+
+ def start_resolve(self, host, type):
+ pass
+
+
+class LibAsyncNSResolver(CommonResolver):
+ '''
+ Asynchronous resolver using libasyncns-python. process() method has to be called
+ in order to proceed the pending requests.
+ Based on patch submitted by Damien Thebault.
+ '''
+ def __init__(self):
+ self.asyncns = libasyncns.Asyncns()
+ CommonResolver.__init__(self)
+
+ def start_resolve(self, host, type):
+ type = libasyncns.ns_t_srv
+ if type == 'txt': type = libasyncns.ns_t_txt
+ resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type)
+ resq.userdata = {'host':host, 'type':type}
+
+ # getaddrinfo to be done
+ #def resolve_name(self, dname, callback):
+ #resq = self.asyncns.getaddrinfo(dname)
+ #resq.userdata = {'callback':callback, 'dname':dname}
+
+ def _on_ready(self, host, type, result_list):
+ if type == libasyncns.ns_t_srv: type = 'srv'
+ elif type == libasyncns.ns_t_txt: type = 'txt'
+
+ CommonResolver._on_ready(self, host, type, result_list)
+
+
+ def process(self):
+ try:
+ self.asyncns.wait(False)
+ resq = self.asyncns.get_next()
+ except:
+ return True
+ if type(resq) == libasyncns.ResQuery:
+ # TXT or SRV result
+ while resq is not None:
+ try:
+ rl = resq.get_done()
+ except:
+ rl = []
+ if rl:
+ for r in rl:
+ r['prio'] = r['pref']
+ self._on_ready(
+ host = resq.userdata['host'],
+ type = resq.userdata['type'],
+ result_list = rl)
+ try:
+ resq = self.asyncns.get_next()
+ except:
+ resq = None
+ elif type(resq) == libasyncns.AddrInfoQuery:
+ # getaddrinfo result (A or AAAA)
+ rl = resq.get_done()
+ resq.userdata['callback'](resq.userdata['dname'], rl)
+ return True
+
+class NSLookupResolver(CommonResolver):
+ '''
+ Asynchronous DNS resolver calling nslookup. Processing of pending requests
+ is invoked from idlequeue which is watching file descriptor of pipe of stdout
+ of nslookup process.
+ '''
+ def __init__(self, idlequeue):
+ self.idlequeue = idlequeue
+ self.process = False
+ CommonResolver.__init__(self)
+
def parse_srv_result(self, fqdn, result):
- ''' parse the output of nslookup command and return list of
+ ''' parse the output of nslookup command and return list of
properties: 'host', 'port','weight', 'priority' corresponding to the found
srv hosts '''
if os.name == 'nt':
return self._parse_srv_result_nt(fqdn, result)
elif os.name == 'posix':
return self._parse_srv_result_posix(fqdn, result)
-
+
def _parse_srv_result_nt(self, fqdn, result):
# output from win32 nslookup command
- if not result:
+ if not result:
return []
hosts = []
lines = result.replace('\r','').split('\n')
@@ -78,7 +199,7 @@ class Resolver:
hosts.append(current_host)
current_host = None
continue
- prop_type = res[0].strip()
+ prop_type = res[0].strip()
prop_value = res[1].strip()
if prop_type.find('prio') > -1:
try:
@@ -104,26 +225,19 @@ class Resolver:
hosts.append(current_host)
current_host = None
return hosts
-
+
def _parse_srv_result_posix(self, fqdn, result):
# typical output of bind-tools nslookup command:
# _xmpp-client._tcp.jabber.org service = 30 30 5222 jabber.org.
- if not result:
+ if not result:
return []
- ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name
hosts = []
lines = result.split('\n')
for line in lines:
if line == '':
continue
- domain = None
if line.startswith(fqdn):
- domain = fqdn
- elif helpers.decode_string(line).startswith(ufqdn):
- line = helpers.decode_string(line)
- domain = ufqdn
- if domain:
- rest = line[len(domain):].split('=')
+ rest = line[len(fqdn):].split('=')
if len(rest) != 2:
continue
answer_type, props_str = rest
@@ -144,72 +258,49 @@ class Resolver:
hosts.append({'host': host, 'port': port,'weight': weight,
'prio': prio})
return hosts
-
- def _on_ready(self, host, result):
+
+ def _on_ready(self, host, type, result):
# nslookup finished, parse the result and call the handlers
result_list = self.parse_srv_result(host, result)
-
- # practically it is impossible to be the opposite, but who knows :)
- if host not in self.resolved_hosts:
- self.resolved_hosts[host] = result_list
- if host in self.handlers:
- for callback in self.handlers[host]:
- callback(host, result_list)
- del(self.handlers[host])
-
- def start_resolve(self, host):
+ CommonResolver._on_ready(self, host, type, result_list)
+
+
+ def start_resolve(self, host, type):
''' spawn new nslookup process and start waiting for results '''
- ns = NsLookup(self._on_ready, host)
+ ns = NsLookup(self._on_ready, host, type)
ns.set_idlequeue(self.idlequeue)
ns.commandtimeout = 10
ns.start()
-
- def resolve(self, host, on_ready):
- if not host:
- # empty host, return empty list of srv records
- on_ready([])
- return
- if host in self.resolved_hosts:
- # host is already resolved, return cached values
- on_ready(host, self.resolved_hosts[host])
- return
- if host in self.handlers:
- # host is about to be resolved by another connection,
- # attach our callback
- self.handlers[host].append(on_ready)
- else:
- # host has never been resolved, start now
- self.handlers[host] = [on_ready]
- self.start_resolve(host)
+
# TODO: move IdleCommand class in other file, maybe helpers ?
class IdleCommand(IdleObject):
def __init__(self, on_result):
# how long (sec.) to wait for result ( 0 - forever )
# it is a class var, instead of a constant and we can override it.
- self.commandtimeout = 0
+ self.commandtimeout = 0
# when we have some kind of result (valid, ot not) we call this handler
self.result_handler = on_result
# if it is True, we can safetely execute the command
self.canexecute = True
self.idlequeue = None
- self.result = ''
-
+ self.result =''
+
def set_idlequeue(self, idlequeue):
self.idlequeue = idlequeue
-
+
def _return_result(self):
if self.result_handler:
self.result_handler(self.result)
self.result_handler = None
-
+
def _compose_command_args(self):
return ['echo', 'da']
-
+
def _compose_command_line(self):
''' return one line representation of command and its arguments '''
- return ' '.join(self._compose_command_args())
-
+ return reduce(lambda left, right: left + ' ' + right, self._compose_command_args())
+
def wait_child(self):
if self.pipe.poll() is None:
# result timeout
@@ -235,16 +326,16 @@ class IdleCommand(IdleObject):
self._start_nt()
elif os.name == 'posix':
self._start_posix()
-
+
def _start_nt(self):
- # if gajim is started from noninteraactive shells stdin is closed and
+ # if gajim is started from noninteraactive shells stdin is closed and
# cannot be forwarded, so we have to keep it open
- self.pipe = Popen(self._compose_command_args(), stdout=PIPE,
+ self.pipe = Popen(self._compose_command_args(), stdout=PIPE,
bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE)
if self.commandtimeout >= 0:
self.endtime = self.idlequeue.current_time() + self.commandtimeout
self.idlequeue.set_alarm(self.wait_child, 0.1)
-
+
def _start_posix(self):
self.pipe = os.popen(self._compose_command_line())
self.fd = self.pipe.fileno()
@@ -252,39 +343,39 @@ class IdleCommand(IdleObject):
self.idlequeue.plug_idle(self, False, True)
if self.commandtimeout >= 0:
self.idlequeue.set_read_timeout(self.fd, self.commandtimeout)
-
+
def end(self):
self.idlequeue.unplug_idle(self.fd)
try:
self.pipe.close()
- except Exception:
+ except:
pass
-
+
def pollend(self):
self.idlequeue.remove_timeout(self.fd)
self.end()
self._return_result()
-
+
def pollin(self):
try:
res = self.pipe.read()
- except Exception:
+ except Exception, e:
res = ''
if res == '':
return self.pollend()
else:
self.result += res
-
+
def read_timeout(self):
self.end()
self._return_result()
-
+
class NsLookup(IdleCommand):
- def __init__(self, on_result, host='_xmpp-client', type_ = 'srv'):
+ def __init__(self, on_result, host='_xmpp-client', type='srv'):
IdleCommand.__init__(self, on_result)
- self.commandtimeout = 10
+ self.commandtimeout = 10
self.host = host.lower()
- self.type = type_.lower()
+ self.type = type.lower()
if not host_pattern.match(self.host):
# invalid host name
print >> sys.stderr, 'Invalid host: %s' % self.host
@@ -294,15 +385,15 @@ class NsLookup(IdleCommand):
print >> sys.stderr, 'Invalid querytype: %s' % self.type
self.canexecute = False
return
-
+
def _compose_command_args(self):
return ['nslookup', '-type=' + self.type , self.host]
-
+
def _return_result(self):
if self.result_handler:
- self.result_handler(self.host, self.result)
+ self.result_handler(self.host, self.type, self.result)
self.result_handler = None
-
+
# below lines is on how to use API and assist in testing
if __name__ == '__main__':
if os.name == 'posix':
@@ -312,10 +403,11 @@ if __name__ == '__main__':
# testing Resolver class
import gobject
import gtk
-
+
resolver = Resolver(idlequeue)
-
+
def clicked(widget):
+ global resolver
host = text_view.get_text()
def on_result(host, result_array):
print 'Result:\n' + repr(result_array)
@@ -332,17 +424,5 @@ if __name__ == '__main__':
but.connect('clicked', clicked)
win.add(hbox)
win.show_all()
-
- def process():
- try:
- idlequeue.process()
- except Exception:
- # Otherwise, an exception will stop our loop
- gobject.timeout_add(200, process)
- raise
- return True
-
- gobject.timeout_add(200, process)
+ gobject.timeout_add(200, idlequeue.process)
gtk.main()
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/__init__.py b/src/common/xmpp/__init__.py
index 07d4d237b..f0f5069a4 100644
--- a/src/common/xmpp/__init__.py
+++ b/src/common/xmpp/__init__.py
@@ -26,7 +26,8 @@ and use only methods for access all values you should not have any problems.
"""
-import simplexml,protocol,debug,auth_nb,auth,transports,transports_nb,roster_nb,roster,dispatcher_nb,features_nb,features,browser,filetransfer,commands, idlequeue
+import simplexml, protocol, auth_nb, transports_nb, roster_nb
+import dispatcher_nb, features_nb, idlequeue, bosh, tls_nb, proxy_connectors
from client_nb import *
from client import *
from protocol import *
diff --git a/src/common/xmpp/auth.py b/src/common/xmpp/auth.py
deleted file mode 100644
index b76d1c1d6..000000000
--- a/src/common/xmpp/auth.py
+++ /dev/null
@@ -1,314 +0,0 @@
-## auth.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-# $Id: auth.py,v 1.35 2006/01/18 19:26:43 normanr Exp $
-
-"""
-Provides library with all Non-SASL and SASL authentication mechanisms.
-Can be used both for client and transport authentication.
-"""
-
-from protocol import *
-from client import PlugIn
-import sha,base64,random,dispatcher
-
-import md5
-def HH(some): return md5.new(some).hexdigest()
-def H(some): return md5.new(some).digest()
-def C(some): return ':'.join(some)
-
-class NonSASL(PlugIn):
- """ Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and transport authentication."""
- def __init__(self,user,password,resource):
- """ Caches username, password and resource for auth. """
- PlugIn.__init__(self)
- self.DBG_LINE='gen_auth'
- self.user=user
- self.password=password
- self.resource=resource
-
- def plugin(self,owner):
- """ Determine the best auth method (digest/0k/plain) and use it for auth.
- Returns used method name on success. Used internally. """
- if not self.resource: return self.authComponent(owner)
- self.DEBUG('Querying server about possible auth methods','start')
- resp=owner.Dispatcher.SendAndWaitForResponse(Iq('get',NS_AUTH,payload=[Node('username',payload=[self.user])]))
- if not isResultNode(resp):
- self.DEBUG('No result node arrived! Aborting...','error')
- return
- iq=Iq(typ='set',node=resp)
- query=iq.getTag('query')
- query.setTagData('username',self.user)
- query.setTagData('resource',self.resource)
-
- if query.getTag('digest'):
- self.DEBUG("Performing digest authentication",'ok')
- query.setTagData('digest',sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest())
- if query.getTag('password'): query.delChild('password')
- method='digest'
- elif query.getTag('token'):
- token=query.getTagData('token')
- seq=query.getTagData('sequence')
- self.DEBUG("Performing zero-k authentication",'ok')
-
- def hasher(s):
- return sha.new(s).hexdigest()
-
- def hash_n_times(s, count):
- return count and hasher(hash_n_times(s, count-1)) or s
-
- hash_ = hash_n_times(hasher(hasher(self.password)+token), int(seq))
- query.setTagData('hash', hash_)
- method='0k'
- else:
- self.DEBUG("Sequre methods unsupported, performing plain text authentication",'warn')
- query.setTagData('password',self.password)
- method='plain'
- resp=owner.Dispatcher.SendAndWaitForResponse(iq)
- if isResultNode(resp):
- self.DEBUG('Sucessfully authenticated with remove host.','ok')
- owner.User=self.user
- owner.Resource=self.resource
- owner._registered_name=owner.User+'@'+owner.Server+'/'+owner.Resource
- return method
- self.DEBUG('Authentication failed!','error')
-
- def authComponent(self,owner):
- """ Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. """
- self.handshake=0
- owner.send(Node(NS_COMPONENT_ACCEPT+' handshake',payload=[sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()]))
- owner.RegisterHandler('handshake',self.handshakeHandler,xmlns=NS_COMPONENT_ACCEPT)
- while not self.handshake:
- self.DEBUG("waiting on handshake",'notify')
- owner.Process(1)
- owner._registered_name=self.user
- if self.handshake+1: return 'ok'
-
- def handshakeHandler(self,disp,stanza):
- """ Handler for registering in dispatcher for accepting transport authentication. """
- if stanza.getName()=='handshake': self.handshake=1
- else: self.handshake=-1
-
-class SASL(PlugIn):
- """ Implements SASL authentication. """
- def __init__(self,username,password):
- PlugIn.__init__(self)
- self.username=username
- self.password=password
-
- def plugin(self,owner):
- if 'version' not in self._owner.Dispatcher.Stream._document_attrs: self.startsasl='not-supported'
- elif self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else: self.startsasl=None
-
- def auth(self):
- """ Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be
- either "success" or "failure". Note that successfull auth will take at least
- two Dispatcher.Process() calls. """
- if self.startsasl: pass
- elif self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
-
- def plugout(self):
- """ Remove SASL handlers from owner's dispatcher. Used internally. """
- self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- self._owner.UnregisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
- self._owner.UnregisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
- self._owner.UnregisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
-
- def FeaturesHandler(self,conn,feats):
- """ Used to determine if server supports SASL auth. Used internally. """
- if not feats.getTag('mechanisms',namespace=NS_SASL):
- self.startsasl='not-supported'
- self.DEBUG('SASL not supported by server','error')
- return
- mecs=[]
- for mec in feats.getTag('mechanisms',namespace=NS_SASL).getTags('mechanism'):
- mecs.append(mec.getData())
- self._owner.RegisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
- self._owner.RegisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
- self._owner.RegisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
- if "DIGEST-MD5" in mecs:
- node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'DIGEST-MD5'})
- elif "PLAIN" in mecs:
- sasl_data='%s\x00%s\x00%s'%(self.username+'@'+self._owner.Server,self.username,self.password)
- node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},payload=[base64.encodestring(sasl_data)])
- else:
- self.startsasl='failure'
- self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')
- return
- self.startsasl='in-process'
- self._owner.send(node.__str__())
- raise NodeProcessed
-
- def SASLHandler(self,conn,challenge):
- """ Perform next SASL auth step. Used internally. """
- if challenge.getNamespace()!=NS_SASL: return
- if challenge.getName()=='failure':
- self.startsasl='failure'
- try:
- reason=challenge.getChildren()[0]
- except Exception:
- reason=challenge
- self.DEBUG('Failed SASL authentification: %s'%reason,'error')
- raise NodeProcessed
- elif challenge.getName()=='success':
- self.startsasl='success'
- self.DEBUG('Successfully authenticated with remote server.','ok')
- handlers=self._owner.Dispatcher.dumpHandlers()
- self._owner.Dispatcher.PlugOut()
- dispatcher.Dispatcher().PlugIn(self._owner)
- self._owner.Dispatcher.restoreHandlers(handlers)
- self._owner.User=self.username
- raise NodeProcessed
-########################################3333
- incoming_data=challenge.getData()
- chal={}
- data=base64.decodestring(incoming_data)
- self.DEBUG('Got challenge:'+data,'ok')
- for pair in data.split(','):
- key,value=pair.split('=', 1)
- if value.startswith('"') and value.endswith('"'): value=value[1:-1]
- chal[key]=value
- if 'qop' in chal and chal['qop']=='auth':
- resp={}
- resp['username']=self.username
- resp['realm']=self._owner.Server
- resp['nonce']=chal['nonce']
- resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in
- itertools.repeat(random.randint, 7))
- resp['nc']=('00000001')
- resp['qop']='auth'
- resp['digest-uri']='xmpp/'+self._owner.Server
- A1=C([H(C([resp['username'],resp['realm'],self.password])),resp['nonce'],resp['cnonce']])
- A2=C(['AUTHENTICATE',resp['digest-uri']])
- response= HH(C([HH(A1),resp['nonce'],resp['nc'],resp['cnonce'],resp['qop'],HH(A2)]))
- resp['response']=response
- resp['charset']='utf-8'
- sasl_data=''
- for key in ('charset','username','realm','nonce','nc','cnonce','digest-uri','response','qop'):
- if key in ['nc','qop','response','charset']: sasl_data+="%s=%s,"%(key,resp[key])
- else: sasl_data+='%s="%s",'%(key,resp[key])
-########################################3333
- node=Node('response',attrs={'xmlns':NS_SASL},payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')])
- self._owner.send(node.__str__())
- elif 'rspauth' in chal: self._owner.send(Node('response',attrs={'xmlns':NS_SASL}).__str__())
- else:
- self.startsasl='failure'
- self.DEBUG('Failed SASL authentification: unknown challenge','error')
- raise NodeProcessed
-
-class Bind(PlugIn):
- """ Bind some JID to the current connection to allow router know of our location."""
- def __init__(self):
- PlugIn.__init__(self)
- self.DBG_LINE='bind'
- self.bound=None
-
- def plugin(self,owner):
- """ Start resource binding, if allowed at this time. Used internally. """
- if self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
-
- def plugout(self):
- """ Remove Bind handler from owner's dispatcher. Used internally. """
- self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
-
- def FeaturesHandler(self,conn,feats):
- """ Determine if server supports resource binding and set some internal attributes accordingly. """
- if not feats.getTag('bind',namespace=NS_BIND):
- self.bound='failure'
- self.DEBUG('Server does not requested binding.','error')
- return
- if feats.getTag('session',namespace=NS_SESSION): self.session=1
- else: self.session=-1
- self.bound=[]
-
- def Bind(self,resource=None):
- """ Perform binding. Use provided resource name or random (if not provided). """
- while self.bound is None and self._owner.Process(1): pass
- if resource: resource=[Node('resource',payload=[resource])]
- else: resource=[]
- resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('bind',attrs={'xmlns':NS_BIND},payload=resource)]))
- if isResultNode(resp):
- self.bound.append(resp.getTag('bind').getTagData('jid'))
- self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok')
- jid=JID(resp.getTag('bind').getTagData('jid'))
- self._owner.User=jid.getNode()
- self._owner.Resource=jid.getResource()
- resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('session',attrs={'xmlns':NS_SESSION})]))
- if isResultNode(resp):
- self.DEBUG('Successfully opened session.','ok')
- self.session=1
- return 'ok'
- else:
- self.DEBUG('Session open failed.','error')
- self.session=0
- elif resp: self.DEBUG('Binding failed: %s.'%resp.getTag('error'),'error')
- else:
- self.DEBUG('Binding failed: timeout expired.','error')
- return ''
-
-class ComponentBind(PlugIn):
- """ ComponentBind some JID to the current connection to allow router know of our location."""
- def __init__(self):
- PlugIn.__init__(self)
- self.DBG_LINE='bind'
- self.bound=None
- self.needsUnregister=None
-
- def plugin(self,owner):
- """ Start resource binding, if allowed at this time. Used internally. """
- if self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else:
- self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- self.needsUnregister=1
-
- def plugout(self):
- """ Remove ComponentBind handler from owner's dispatcher. Used internally. """
- if self.needsUnregister:
- self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
-
- def FeaturesHandler(self,conn,feats):
- """ Determine if server supports resource binding and set some internal attributes accordingly. """
- if not feats.getTag('bind',namespace=NS_BIND):
- self.bound='failure'
- self.DEBUG('Server does not requested binding.','error')
- return
- if feats.getTag('session',namespace=NS_SESSION): self.session=1
- else: self.session=-1
- self.bound=[]
-
- def Bind(self,domain=None):
- """ Perform binding. Use provided domain name (if not provided). """
- while self.bound is None and self._owner.Process(1): pass
- resp=self._owner.SendAndWaitForResponse(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1))
- if resp and resp.getAttr('error'):
- self.DEBUG('Binding failed: %s.'%resp.getAttr('error'),'error')
- elif resp:
- self.DEBUG('Successfully bound.','ok')
- return 'ok'
- else:
- self.DEBUG('Binding failed: timeout expired.','error')
- return ''
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py
index 495913c2c..af74299c1 100644
--- a/src/common/xmpp/auth_nb.py
+++ b/src/common/xmpp/auth_nb.py
@@ -18,13 +18,20 @@ Provides library with all Non-SASL and SASL authentication mechanisms.
Can be used both for client and transport authentication.
'''
from protocol import *
-from auth import *
from client import PlugIn
import sha
import base64
import random
import itertools
import dispatcher_nb
+import md5
+
+import logging
+log = logging.getLogger('gajim.c.x.auth_nb')
+
+def HH(some): return md5.new(some).hexdigest()
+def H(some): return md5.new(some).digest()
+def C(some): return ':'.join(some)
try:
import kerberos
@@ -137,7 +144,7 @@ class SASL(PlugIn):
''' Used to determine if server supports SASL auth. Used internally. '''
if not feats.getTag('mechanisms', namespace=NS_SASL):
self.startsasl='not-supported'
- self.DEBUG('SASL not supported by server', 'error')
+ log.error('SASL not supported by server')
return
self.mecs=[]
for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags('mechanism'):
@@ -165,13 +172,13 @@ class SASL(PlugIn):
elif 'PLAIN' in self.mecs:
self.mecs.remove('PLAIN')
sasl_data='%s\x00%s\x00%s' % (self.username+'@' + self._owner.Server,
- self.username, self.password)
+ self.username, self.password)
node=Node('auth', attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},
- payload=[base64.encodestring(sasl_data).replace('\n','')])
+ payload=[base64.encodestring(sasl_data).replace('\n','')])
self.mechanism = 'PLAIN'
else:
self.startsasl='failure'
- self.DEBUG('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.', 'error')
+ log.error('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.')
return
self.startsasl='in-process'
self._owner.send(node.__str__())
@@ -187,7 +194,7 @@ class SASL(PlugIn):
reason = challenge.getChildren()[0]
except Exception:
reason = challenge
- self.DEBUG('Failed SASL authentification: %s' % reason, 'error')
+ log.error('Failed SASL authentification: %s' % reason)
if len(self.mecs) > 0:
# There are other mechanisms to test
self.MechanismHandler()
@@ -197,19 +204,25 @@ class SASL(PlugIn):
raise NodeProcessed
elif challenge.getName() == 'success':
self.startsasl='success'
- self.DEBUG('Successfully authenticated with remote server.', 'ok')
+ log.info('Successfully authenticated with remote server.')
handlers=self._owner.Dispatcher.dumpHandlers()
+
+ # save old features. They will be used in case we won't get response on
+ # stream restart after SASL auth (happens with XMPP over BOSH with Openfire)
+ old_features = self._owner.Dispatcher.Stream.features
+
self._owner.Dispatcher.PlugOut()
- dispatcher_nb.Dispatcher().PlugIn(self._owner)
+ dispatcher_nb.Dispatcher().PlugIn(self._owner, after_SASL=True,
+ old_features=old_features)
self._owner.Dispatcher.restoreHandlers(handlers)
self._owner.User = self.username
if self.on_sasl :
- self.on_sasl ()
+ self.on_sasl()
raise NodeProcessed
########################################3333
incoming_data = challenge.getData()
data=base64.decodestring(incoming_data)
- self.DEBUG('Got challenge:'+data,'ok')
+ log.info('Got challenge:' + data)
if self.mechanism == 'GSSAPI':
if self.gss_step == GSS_STATE_STEP:
rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data)
@@ -265,7 +278,7 @@ class SASL(PlugIn):
self._owner.send(Node('response', attrs={'xmlns':NS_SASL}).__str__())
else:
self.startsasl='failure'
- self.DEBUG('Failed SASL authentification: unknown challenge', 'error')
+ log.error('Failed SASL authentification: unknown challenge')
if self.on_sasl :
self.on_sasl ()
raise NodeProcessed
@@ -277,7 +290,6 @@ class NonBlockingNonSASL(PlugIn):
def __init__(self, user, password, resource, on_auth):
''' Caches username, password and resource for auth. '''
PlugIn.__init__(self)
- self.DBG_LINE ='gen_auth'
self.user = user
self.password= password
self.resource = resource
@@ -286,9 +298,7 @@ class NonBlockingNonSASL(PlugIn):
def plugin(self, owner):
''' Determine the best auth method (digest/0k/plain) and use it for auth.
Returns used method name on success. Used internally. '''
- if not self.resource:
- return self.authComponent(owner)
- self.DEBUG('Querying server about possible auth methods', 'start')
+ log.info('Querying server about possible auth methods')
self.owner = owner
owner.Dispatcher.SendAndWaitForResponse(
@@ -297,7 +307,7 @@ class NonBlockingNonSASL(PlugIn):
def _on_username(self, resp):
if not isResultNode(resp):
- self.DEBUG('No result node arrived! Aborting...','error')
+ log.error('No result node arrived! Aborting...')
return self.on_auth(None)
iq=Iq(typ='set',node=resp)
query=iq.getTag('query')
@@ -305,7 +315,7 @@ class NonBlockingNonSASL(PlugIn):
query.setTagData('resource',self.resource)
if query.getTag('digest'):
- self.DEBUG("Performing digest authentication",'ok')
+ log.info("Performing digest authentication")
query.setTagData('digest',
sha.new(self.owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest())
if query.getTag('password'):
@@ -314,7 +324,7 @@ class NonBlockingNonSASL(PlugIn):
elif query.getTag('token'):
token=query.getTagData('token')
seq=query.getTagData('sequence')
- self.DEBUG("Performing zero-k authentication",'ok')
+ log.info("Performing zero-k authentication")
def hasher(s):
return sha.new(s).hexdigest()
@@ -326,51 +336,28 @@ class NonBlockingNonSASL(PlugIn):
query.setTagData('hash', hash_)
self._method='0k'
else:
- self.DEBUG("Sequre methods unsupported, performing plain text authentication",'warn')
+ log.warn("Sequre methods unsupported, performing plain text authentication")
query.setTagData('password',self.password)
self._method='plain'
resp=self.owner.Dispatcher.SendAndWaitForResponse(iq, func=self._on_auth)
def _on_auth(self, resp):
if isResultNode(resp):
- self.DEBUG('Sucessfully authenticated with remove host.','ok')
+ log.info('Sucessfully authenticated with remove host.')
self.owner.User=self.user
self.owner.Resource=self.resource
self.owner._registered_name=self.owner.User+'@'+self.owner.Server+'/'+self.owner.Resource
return self.on_auth(self._method)
- self.DEBUG('Authentication failed!','error')
+ log.error('Authentication failed!')
return self.on_auth(None)
- def authComponent(self,owner):
- ''' Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. '''
- self.handshake=0
- owner.send(Node(NS_COMPONENT_ACCEPT+' handshake',
- payload=[sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()]))
- owner.RegisterHandler('handshake', self.handshakeHandler, xmlns=NS_COMPONENT_ACCEPT)
- self._owner.onreceive(self._on_auth_component)
- def _on_auth_component(self, data):
- ''' called when we receive some response, after we send the handshake '''
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not self.handshake:
- self.DEBUG('waiting on handshake', 'notify')
- return
- self._owner.onreceive(None)
- self._owner._registered_name=self.user
- if self.handshake+1:
- return self.on_auth('ok')
- self.on_auth(None)
-
- def handshakeHandler(self,disp,stanza):
- ''' Handler for registering in dispatcher for accepting transport authentication. '''
- if stanza.getName() == 'handshake':
- self.handshake=1
- else:
- self.handshake=-1
-
-class NonBlockingBind(Bind):
+class NonBlockingBind(PlugIn):
''' Bind some JID to the current connection to allow router know of our location.'''
+ def __init__(self):
+ PlugIn.__init__(self)
+ self.bound=None
+
def plugin(self, owner):
''' Start resource binding, if allowed at this time. Used internally. '''
if self._owner.Dispatcher.Stream.features:
@@ -378,7 +365,22 @@ class NonBlockingBind(Bind):
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
except NodeProcessed:
pass
- else: self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
+ else:
+ self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
+
+ def FeaturesHandler(self,conn,feats):
+ ''' Determine if server supports resource binding and set some internal attributes accordingly. '''
+ if not feats.getTag('bind',namespace=NS_BIND):
+ log.error('Server does not requested binding.')
+ # we try to bind resource anyway
+ #self.bound='failure'
+ self.bound=[]
+ return
+ if feats.getTag('session',namespace=NS_SESSION):
+ self.session=1
+ else:
+ self.session=-1
+ self.bound=[]
def plugout(self):
''' Remove Bind handler from owner's dispatcher. Used internally. '''
@@ -401,86 +403,37 @@ class NonBlockingBind(Bind):
def _on_bound(self, resp):
if isResultNode(resp):
self.bound.append(resp.getTag('bind').getTagData('jid'))
- self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok')
+ log.info('Successfully bound %s.'%self.bound[-1])
jid=JID(resp.getTag('bind').getTagData('jid'))
self._owner.User=jid.getNode()
self._owner.Resource=jid.getResource()
self._owner.SendAndWaitForResponse(Protocol('iq', typ='set',
payload=[Node('session', attrs={'xmlns':NS_SESSION})]), func=self._on_session)
elif resp:
- self.DEBUG('Binding failed: %s.' % resp.getTag('error'),'error')
+ log.error('Binding failed: %s.' % resp.getTag('error'))
self.on_bound(None)
else:
- self.DEBUG('Binding failed: timeout expired.', 'error')
+ log.error('Binding failed: timeout expired.')
self.on_bound(None)
def _on_session(self, resp):
self._owner.onreceive(None)
if isResultNode(resp):
- self.DEBUG('Successfully opened session.', 'ok')
+ log.info('Successfully opened session.')
self.session = 1
self.on_bound('ok')
else:
- self.DEBUG('Session open failed.', 'error')
+ log.error('Session open failed.')
self.session = 0
self.on_bound(None)
self._owner.onreceive(None)
if isResultNode(resp):
- self.DEBUG('Successfully opened session.', 'ok')
+ log.info('Successfully opened session.')
self.session = 1
self.on_bound('ok')
else:
- self.DEBUG('Session open failed.', 'error')
+ log.error('Session open failed.')
self.session = 0
self.on_bound(None)
-class NBComponentBind(ComponentBind):
- ''' ComponentBind some JID to the current connection to allow
- router know of our location.
- '''
- def plugin(self,owner):
- ''' Start resource binding, if allowed at this time. Used internally. '''
- if self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
- self.needsUnregister = 1
-
- def plugout(self):
- ''' Remove ComponentBind handler from owner's dispatcher. Used internally. '''
- if self.needsUnregister:
- self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
-
- def Bind(self, domain = None, on_bind = None):
- ''' Perform binding. Use provided domain name (if not provided). '''
- def wrapper(resp):
- self._on_bound(resp, domain)
- self._owner.onreceive(wrapper)
- self.on_bind = on_bind
-
- def _on_bound(self, resp, domain=None):
- if resp:
- self.Dispatcher.ProcessNonBlocking(resp)
- if self.bound is None:
- return
- self._owner.onreceive(None)
- self._owner.SendAndWaitForResponse(
- Protocol('bind', attrs={'name':domain}, xmlns=NS_COMPONENT_1),
- func=self._on_bind_reponse)
-
- def _on_bind_reponse(self, resp):
- if resp and resp.getAttr('error'):
- self.DEBUG('Binding failed: %s.' % resp.getAttr('error'), 'error')
- elif resp:
- self.DEBUG('Successfully bound.', 'ok')
- if self.on_bind:
- self.on_bind('ok')
- else:
- self.DEBUG('Binding failed: timeout expired.', 'error')
- if self.on_bind:
- self.on_bind(None)
-
# vim: se ts=3:
diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py
new file mode 100644
index 000000000..ab42c02ad
--- /dev/null
+++ b/src/common/xmpp/bosh.py
@@ -0,0 +1,564 @@
+## bosh.py
+##
+##
+## Copyright (C) 2008 Tomas Karasek
+##
+## This file is part of Gajim.
+##
+## Gajim 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 3 only.
+##
+## Gajim 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.
+##
+## You should have received a copy of the GNU General Public License
+## along with Gajim. If not, see .
+
+
+import locale, random, sha
+from transports_nb import NonBlockingTransport, NonBlockingHTTPBOSH,\
+ CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\
+ urisplit, DISCONNECT_TIMEOUT_SECONDS
+from protocol import BOSHBody
+from simplexml import Node
+
+import logging
+log = logging.getLogger('gajim.c.x.bosh')
+
+KEY_COUNT = 10
+
+# Fake file descriptor - it's used for setting read_timeout in idlequeue for
+# BOSH Transport. In TCP-derived transports this is file descriptor of socket.
+FAKE_DESCRIPTOR = -1337
+
+
+class NonBlockingBOSH(NonBlockingTransport):
+ def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
+ xmpp_server, domain, bosh_dict, proxy_creds):
+ NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
+ estabilish_tls, certs)
+
+ self.bosh_sid = None
+ if locale.getdefaultlocale()[0]:
+ self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0]
+ else:
+ self.bosh_xml_lang = 'en'
+
+ self.http_version = 'HTTP/1.1'
+ self.http_persistent = True
+ self.http_pipelining = bosh_dict['bosh_http_pipelining']
+ self.bosh_to = domain
+
+ self.route_host, self.route_port = xmpp_server
+
+ self.bosh_wait = bosh_dict['bosh_wait']
+ if not self.http_pipelining:
+ self.bosh_hold = 1
+ else:
+ self.bosh_hold = bosh_dict['bosh_hold']
+ self.bosh_requests = self.bosh_hold
+ self.bosh_uri = bosh_dict['bosh_uri']
+ self.bosh_port = bosh_dict['bosh_port']
+ self.bosh_content = bosh_dict['bosh_content']
+ self.over_proxy = bosh_dict['bosh_useproxy']
+ if estabilish_tls:
+ self.bosh_secure = 'true'
+ else:
+ self.bosh_secure = 'false'
+ self.use_proxy_auth = bosh_dict['useauth']
+ self.proxy_creds = proxy_creds
+ self.wait_cb_time = None
+ self.http_socks = []
+ self.stanza_buffer = []
+ self.prio_bosh_stanzas = []
+ self.current_recv_handler = None
+ self.current_recv_socket = None
+ self.key_stack = None
+ self.ack_checker = None
+ self.after_init = False
+ self.proxy_dict = {}
+ if self.over_proxy and self.estabilish_tls:
+ self.proxy_dict['type'] = 'http'
+ # with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to
+ # BOSH Connection Manager
+ self.proxy_dict['xmpp_server'] = (urisplit(self.bosh_uri)[1], self.bosh_port)
+ self.proxy_dict['credentials'] = self.proxy_creds
+
+
+ def connect(self, conn_5tuple, on_connect, on_connect_failure):
+ NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
+
+ global FAKE_DESCRIPTOR
+ FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1
+ self.fd = FAKE_DESCRIPTOR
+
+ self.stanza_buffer = []
+ self.prio_bosh_stanzas = []
+
+ self.key_stack = KeyStack(KEY_COUNT)
+ self.ack_checker = AckChecker()
+ self.after_init = True
+
+ self.http_socks.append(self.get_new_http_socket())
+ self.tcp_connecting_started()
+
+ self.http_socks[0].connect(
+ conn_5tuple = conn_5tuple,
+ on_connect = self._on_connect,
+ on_connect_failure = self._on_connect_failure)
+
+ def _on_connect(self):
+ self.peerhost = self.http_socks[0].peerhost
+ self.ssl_lib = self.http_socks[0].ssl_lib
+ NonBlockingTransport._on_connect(self)
+
+
+
+ def set_timeout(self, timeout):
+ if self.get_state() != DISCONNECTED and self.fd != -1:
+ NonBlockingTransport.set_timeout(self, timeout)
+ else:
+ log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd))
+
+ def on_http_request_possible(self):
+ '''
+ Called when HTTP request it's possible to send a HTTP request. It can be when
+ socket is connected or when HTTP response arrived.
+ There should be always one pending request to BOSH CM.
+ '''
+ log.debug('on_http_req possible, state:\n%s' % self.get_current_state())
+ if self.get_state()==DISCONNECTED: return
+
+ #Hack for making the non-secure warning dialog work
+ if self._owner.got_features:
+ if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')):
+ self.send_BOSH(None)
+ else:
+ # If we already got features and no auth module was plugged yet, we are
+ # probably waiting for confirmation of the "not-secure-connection" dialog.
+ # We don't send HTTP request in that case.
+ # see http://lists.jabber.ru/pipermail/ejabberd/2008-August/004027.html
+ return
+ else:
+ self.send_BOSH(None)
+
+
+
+ def get_socket_in(self, state):
+ ''' gets sockets in desired state '''
+ for s in self.http_socks:
+ if s.get_state()==state: return s
+ return None
+
+
+ def get_free_socket(self):
+ ''' Selects and returns socket eligible for sending a data to.'''
+ if self.http_pipelining:
+ return self.get_socket_in(CONNECTED)
+ else:
+ last_recv_time, tmpsock = 0, None
+ for s in self.http_socks:
+ # we're interested only in CONNECTED socket with no requests pending
+ if s.get_state()==CONNECTED and s.pending_requests==0:
+ # if there's more of them, we want the one with the least recent data receive
+ # (lowest last_recv_time)
+ if (last_recv_time==0) or (s.last_recv_time < last_recv_time):
+ last_recv_time = s.last_recv_time
+ tmpsock = s
+ if tmpsock:
+ return tmpsock
+ else:
+ return None
+
+
+ def send_BOSH(self, payload):
+ '''
+ Tries to send a stanza in payload by appeding it to a buffer and plugging a
+ free socket for writing.
+ '''
+ total_pending_reqs = sum([s.pending_requests for s in self.http_socks])
+
+ # when called after HTTP response (Payload=None) and when there are already
+ # some pending requests and no data to send, or when the socket is
+ # disconnected, we do nothing
+ if payload is None and \
+ total_pending_reqs > 0 and \
+ self.stanza_buffer == [] and \
+ self.prio_bosh_stanzas == [] or \
+ self.get_state()==DISCONNECTED:
+ return
+
+ # now the payload is put to buffer and will be sent at some point
+ self.append_stanza(payload)
+
+ # if we're about to make more requests than allowed, we don't send - stanzas will be
+ # sent after HTTP response from CM, exception is when we're disconnecting - then we
+ # send anyway
+ if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING:
+ log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' %
+ self.get_current_state())
+ return
+
+ # when there's free CONNECTED socket, we plug it for write and the data will
+ # be sent when write is possible
+ if self.get_free_socket():
+ self.plug_socket()
+ return
+
+ # if there is a connecting socket, we just wait for when it connects,
+ # payload will be sent in a sec when the socket connects
+ if self.get_socket_in(CONNECTING): return
+
+ # being here means there are either DISCONNECTED sockets or all sockets are
+ # CONNECTED with too many pending requests
+ s = self.get_socket_in(DISCONNECTED)
+
+ # if we have DISCONNECTED socket, lets connect it and plug for send
+ if s:
+ self.connect_and_flush(s)
+ else:
+ # otherwise create and connect a new one
+ ss = self.get_new_http_socket()
+ self.http_socks.append(ss)
+ self.connect_and_flush(ss)
+ return
+
+ def plug_socket(self):
+ stanza = None
+ s = self.get_free_socket()
+ if s:
+ s._plug_idle(writable=True, readable=True)
+ else:
+ log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())')
+
+ def build_stanza(self, socket):
+ '''
+ Builds a BOSH body tag from data in buffers and adds key, rid and ack
+ attributes to it.
+ This method is called from _do_send() of underlying transport. This is to
+ ensure rid and keys will be processed in correct order. If I generate them
+ before plugging a socket for write (and did it for two sockets/HTTP
+ connections) in parallel, they might be sent in wrong order, which results
+ in violating the BOSH session and server-side disconnect.
+ '''
+ if self.prio_bosh_stanzas:
+ stanza, add_payload = self.prio_bosh_stanzas.pop(0)
+ if add_payload:
+ stanza.setPayload(self.stanza_buffer)
+ self.stanza_buffer = []
+ else:
+ stanza = self.boshify_stanzas(self.stanza_buffer)
+ self.stanza_buffer = []
+
+ stanza = self.ack_checker.backup_stanza(stanza, socket)
+
+ key, newkey = self.key_stack.get()
+ if key:
+ stanza.setAttr('key', key)
+ if newkey:
+ stanza.setAttr('newkey', newkey)
+
+
+ log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket)))
+ self.renew_bosh_wait_timeout(self.bosh_wait + 3)
+ return stanza
+
+
+ def on_bosh_wait_timeout(self):
+ log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait)
+ self.disconnect()
+
+
+ def renew_bosh_wait_timeout(self, timeout):
+ if self.wait_cb_time is not None:
+ self.remove_bosh_wait_timeout()
+ sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout)
+ self.wait_cb_time = sched_time
+
+ def remove_bosh_wait_timeout(self):
+ self.idlequeue.remove_alarm(
+ self.on_bosh_wait_timeout,
+ self.wait_cb_time)
+
+ def on_persistent_fallback(self, socket):
+ '''
+ Called from underlying transport when server closes TCP connection.
+ :param socket: disconnected transport object
+ '''
+ if socket.http_persistent:
+ log.warn('Fallback to nonpersistent HTTP (no pipelining as well)')
+ socket.http_persistent = False
+ self.http_persistent = False
+ self.http_pipelining = False
+ socket.disconnect(do_callback=False)
+ self.connect_and_flush(socket)
+ else:
+ socket.disconnect()
+
+
+
+ def handle_body_attrs(self, stanza_attrs):
+ '''
+ Called for each incoming body stanza from dispatcher. Checks body attributes.
+ '''
+ self.remove_bosh_wait_timeout()
+
+ if self.after_init:
+ if stanza_attrs.has_key('sid'):
+ # session ID should be only in init response
+ self.bosh_sid = stanza_attrs['sid']
+
+ if stanza_attrs.has_key('requests'):
+ self.bosh_requests = int(stanza_attrs['requests'])
+
+ if stanza_attrs.has_key('wait'):
+ self.bosh_wait = int(stanza_attrs['wait'])
+ self.after_init = False
+
+ ack = None
+ if stanza_attrs.has_key('ack'):
+ ack = stanza_attrs['ack']
+ self.ack_checker.process_incoming_ack(ack=ack,
+ socket=self.current_recv_socket)
+
+ if stanza_attrs.has_key('type'):
+ if stanza_attrs['type'] in ['terminate', 'terminal']:
+ condition = 'n/a'
+ if stanza_attrs.has_key('condition'):
+ condition = stanza_attrs['condition']
+ if condition == 'n/a':
+ log.info('Received sesion-ending terminating stanza')
+ else:
+ log.error('Received terminating stanza: %s - %s' % (condition,
+ bosh_errors[condition]))
+ self.disconnect()
+ return
+
+ if stanza_attrs['type'] == 'error':
+ # recoverable error
+ pass
+ return
+
+
+ def append_stanza(self, stanza):
+ ''' appends stanza to a buffer to send '''
+ if stanza:
+ if isinstance(stanza, tuple):
+ # stanza is tuple of BOSH stanza and bool value for whether to add payload
+ self.prio_bosh_stanzas.append(stanza)
+ else:
+ # stanza is XMPP stanza. Will be boshified before send.
+ self.stanza_buffer.append(stanza)
+
+
+ def send(self, stanza, now=False):
+ self.send_BOSH(stanza)
+
+
+
+ def get_current_state(self):
+ t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n'
+ for s in self.http_socks:
+ t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.get_state(), s.pending_requests)
+ t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \
+ % (t, self.prio_bosh_stanzas, self.stanza_buffer,
+ self.ack_checker.get_not_acked_rids())
+ return t
+
+
+ def connect_and_flush(self, socket):
+ socket.connect(
+ conn_5tuple = self.conn_5tuple,
+ on_connect = self.on_http_request_possible,
+ on_connect_failure = self.disconnect)
+
+
+ def boshify_stanzas(self, stanzas=[], body_attrs=None):
+ ''' wraps zero to many stanzas by body tag with xmlns and sid '''
+ log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas))
+ tag = BOSHBody(attrs={'sid': self.bosh_sid})
+ tag.setPayload(stanzas)
+ return tag
+
+
+ def send_init(self, after_SASL=False):
+ if after_SASL:
+ t = BOSHBody(
+ attrs={ 'to': self.bosh_to,
+ 'sid': self.bosh_sid,
+ 'xml:lang': self.bosh_xml_lang,
+ 'xmpp:restart': 'true',
+ 'secure': self.bosh_secure,
+ 'xmlns:xmpp': 'urn:xmpp:xbosh'})
+ else:
+ t = BOSHBody(
+ attrs={ 'content': self.bosh_content,
+ 'hold': str(self.bosh_hold),
+ 'route': '%s:%s' % (self.route_host, self.route_port),
+ 'to': self.bosh_to,
+ 'wait': str(self.bosh_wait),
+ 'xml:lang': self.bosh_xml_lang,
+ 'xmpp:version': '1.0',
+ 'ver': '1.6',
+ 'xmlns:xmpp': 'urn:xmpp:xbosh'})
+ self.send_BOSH((t,True))
+
+ def start_disconnect(self):
+ NonBlockingTransport.start_disconnect(self)
+ self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS)
+ self.send_BOSH(
+ (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True))
+
+
+ def get_new_http_socket(self):
+ http_dict = {'http_uri': self.bosh_uri,
+ 'http_port': self.bosh_port,
+ 'http_version': self.http_version,
+ 'http_persistent': self.http_persistent,
+ 'add_proxy_headers': self.over_proxy and not self.estabilish_tls}
+ if self.use_proxy_auth:
+ http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds
+
+ s = NonBlockingHTTPBOSH(
+ raise_event=self.raise_event,
+ on_disconnect=self.disconnect,
+ idlequeue = self.idlequeue,
+ estabilish_tls = self.estabilish_tls,
+ certs = self.certs,
+ on_http_request_possible = self.on_http_request_possible,
+ http_dict = http_dict,
+ proxy_dict = self.proxy_dict,
+ on_persistent_fallback = self.on_persistent_fallback)
+
+ s.onreceive(self.on_received_http)
+ s.set_stanza_build_cb(self.build_stanza)
+ return s
+
+
+ def onreceive(self, recv_handler):
+ if recv_handler is None:
+ recv_handler = self._owner.Dispatcher.ProcessNonBlocking
+ self.current_recv_handler = recv_handler
+
+
+ def on_received_http(self, data, socket):
+ self.current_recv_socket = socket
+ self.current_recv_handler(data)
+
+
+ def disconnect(self, do_callback=True):
+ self.remove_bosh_wait_timeout()
+ if self.get_state() == DISCONNECTED: return
+ self.fd = -1
+ for s in self.http_socks:
+ s.disconnect(do_callback=False)
+ NonBlockingTransport.disconnect(self, do_callback)
+
+
+def get_rand_number():
+ # with 50-bit random initial rid, session would have to go up
+ # to 7881299347898368 messages to raise rid over 2**53
+ # (see http://www.xmpp.org/extensions/xep-0124.html#rids)
+ # it's also used for sequence key initialization
+ r = random.Random()
+ r.seed()
+ return r.getrandbits(50)
+
+
+
+class AckChecker():
+ '''
+ Class for generating rids and generating and checking acknowledgements in
+ BOSH messages.
+ '''
+ def __init__(self):
+ self.rid = get_rand_number()
+ self.ack = 1
+ self.last_rids = {}
+ self.not_acked = []
+
+
+ def get_not_acked_rids(self): return [rid for rid, st in self.not_acked]
+
+ def backup_stanza(self, stanza, socket):
+ socket.pending_requests += 1
+ rid = self.get_rid()
+ self.not_acked.append((rid, stanza))
+ stanza.setAttr('rid', str(rid))
+ self.last_rids[socket]=rid
+
+ if self.rid != self.ack + 1:
+ stanza.setAttr('ack', str(self.ack))
+ return stanza
+
+ def process_incoming_ack(self, socket, ack=None):
+ socket.pending_requests -= 1
+ if ack:
+ ack = int(ack)
+ else:
+ ack = self.last_rids[socket]
+
+ i = len([rid for rid, st in self.not_acked if ack >= rid])
+ self.not_acked = self.not_acked[i:]
+
+ self.ack = ack
+
+
+ def get_rid(self):
+ self.rid = self.rid + 1
+ return self.rid
+
+
+
+
+
+class KeyStack():
+ '''
+ Class implementing key sequences for BOSH messages
+ '''
+ def __init__(self, count):
+ self.count = count
+ self.keys = []
+ self.reset()
+ self.first_call = True
+
+ def reset(self):
+ seed = str(get_rand_number())
+ self.keys = [sha.new(seed).hexdigest()]
+ for i in range(self.count-1):
+ curr_seed = self.keys[i]
+ self.keys.append(sha.new(curr_seed).hexdigest())
+
+ def get(self):
+ if self.first_call:
+ self.first_call = False
+ return (None, self.keys.pop())
+
+ if len(self.keys)>1:
+ return (self.keys.pop(), None)
+ else:
+ last_key = self.keys.pop()
+ self.reset()
+ new_key = self.keys.pop()
+ return (last_key, new_key)
+
+# http://www.xmpp.org/extensions/xep-0124.html#errorstatus-terminal
+bosh_errors = {
+ 'n/a': 'none or unknown condition in terminating body stanza',
+ 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.',
+ 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.',
+ 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.',
+ 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.',
+ 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.',
+ 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid',
+ 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.',
+ 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).',
+ 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.',
+ 'remote-stream-error': 'Encapsulates an error in the protocol being transported.',
+ 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the child element.',
+ 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.',
+ 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the wrapper.'
+}
diff --git a/src/common/xmpp/browser.py b/src/common/xmpp/browser.py
deleted file mode 100644
index 04fa649b6..000000000
--- a/src/common/xmpp/browser.py
+++ /dev/null
@@ -1,221 +0,0 @@
-## browser.py
-##
-## Copyright (C) 2004 Alexey "Snake" Nezhdanov
-##
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-# $Id: browser.py,v 1.11 2005/10/07 23:17:09 normanr Exp $
-
-"""Browser module provides DISCO server framework for your application.
-This functionality can be used for very different purposes - from publishing
-software version and supported features to building of "jabber site" that users
-can navigate with their disco browsers and interact with active content.
-
-Such functionality is achieved via registering "DISCO handlers" that are
-automatically called when user requests some node of your disco tree.
-"""
-
-from dispatcher import *
-from client import PlugIn
-
-DBG_BROWSER = "Browser"
-
-class Browser(PlugIn):
- """ WARNING! This class is for components only. It will not work in client mode!
-
- Standart xmpppy class that is ancestor of PlugIn and can be attached
- to your application.
- All processing will be performed in the handlers registered in the browser
- instance. You can register any number of handlers ensuring that for each
- node/jid combination only one (or none) handler registered.
- You can register static information or the fully-blown function that will
- calculate the answer dynamically.
- Example of static info (see JEP-0030, examples 13-14):
- # cl - your xmpppy connection instance.
- b=xmpp.browser.Browser()
- b.PlugIn(cl)
- items=[]
- item={}
- item['jid']='catalog.shakespeare.lit'
- item['node']='books'
- item['name']='Books by and about Shakespeare'
- items.append(item)
- item={}
- item['jid']='catalog.shakespeare.lit'
- item['node']='clothing'
- item['name']='Wear your literary taste with pride'
- items.append(item)
- item={}
- item['jid']='catalog.shakespeare.lit'
- item['node']='music'
- item['name']='Music from the time of Shakespeare'
- items.append(item)
- info={'ids':[], 'features':[]}
- b.setDiscoHandler({'items':items,'info':info})
-
- items should be a list of item elements.
- every item element can have any of these four keys: 'jid', 'node', 'name', 'action'
- info should be a dicionary and must have keys 'ids' and 'features'.
- Both of them should be lists:
- ids is a list of dictionaries and features is a list of text strings.
- Example (see JEP-0030, examples 1-2)
- # cl - your xmpppy connection instance.
- b=xmpp.browser.Browser()
- b.PlugIn(cl)
- items=[]
- ids=[]
- ids.append({'category':'conference','type':'text','name':'Play-Specific Chatrooms'})
- ids.append({'category':'directory','type':'chatroom','name':'Play-Specific Chatrooms'})
- features=[NS_DISCO_INFO,NS_DISCO_ITEMS,NS_MUC,NS_REGISTER,NS_SEARCH,NS_TIME,NS_VERSION]
- info={'ids':ids,'features':features}
- # info['xdata']=xmpp.protocol.DataForm() # JEP-0128
- b.setDiscoHandler({'items':[],'info':info})
- """
- def __init__(self):
- """Initialises internal variables. Used internally."""
- PlugIn.__init__(self)
- self.DBG_LINE = DBG_BROWSER
- self._exported_methods=[]
- self._handlers={'':{}}
-
- def plugin(self, owner):
- """ Registers it's own iq handlers in your application dispatcher instance.
- Used internally."""
- owner.RegisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_INFO)
- owner.RegisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_ITEMS)
- owner.debug_flags.append(DBG_BROWSER)
-
- def plugout(self):
- """ Unregisters browser's iq handlers from your application dispatcher instance.
- Used internally."""
- self._owner.UnregisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_INFO)
- self._owner.UnregisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_ITEMS)
-
- def _traversePath(self,node,jid,set_=0):
- """ Returns dictionary and key or None,None
- None - root node (w/o "node" attribute)
- /a/b/c - node
- /a/b/ - branch
- Set returns '' or None as the key
- get returns '' or None as the key or None as the dict.
- Used internally."""
- if jid in self._handlers: cur=self._handlers[jid]
- elif set_:
- self._handlers[jid]={}
- cur=self._handlers[jid]
- else: cur=self._handlers['']
- if node is None: node=[None]
- else: node=node.replace('/',' /').split('/')
- for i in node:
- if i!='' and i in cur: cur=cur[i]
- elif set_ and i!='': cur[i]={dict:cur,str:i}; cur=cur[i]
- elif set_ or '' in cur: return cur,''
- else: return None,None
- if 1 in cur or set_: return cur,1
- raise Exception("Corrupted data")
-
- def setDiscoHandler(self,handler,node='',jid=''):
- """ This is the main method that you will use in this class.
- It is used to register supplied DISCO handler (or dictionary with static info)
- as handler of some disco tree branch.
- If you do not specify the node this handler will be used for all queried nodes.
- If you do not specify the jid this handler will be used for all queried JIDs.
-
- Usage:
- cl.Browser.setDiscoHandler(someDict,node,jid)
- or
- cl.Browser.setDiscoHandler(someDISCOHandler,node,jid)
- where
-
- someDict={
- 'items':[
- {'jid':'jid1','action':'action1','node':'node1','name':'name1'},
- {'jid':'jid2','action':'action2','node':'node2','name':'name2'},
- {'jid':'jid3','node':'node3','name':'name3'},
- {'jid':'jid4','node':'node4'}
- ],
- 'info' :{
- 'ids':[
- {'category':'category1','type':'type1','name':'name1'},
- {'category':'category2','type':'type2','name':'name2'},
- {'category':'category3','type':'type3','name':'name3'},
- ],
- 'features':['feature1','feature2','feature3','feature4'],
- 'xdata':DataForm
- }
- }
-
- and/or
-
- def someDISCOHandler(session,request,TYR):
- # if TYR=='items': # returns items list of the same format as shown above
- # elif TYR=='info': # returns info dictionary of the same format as shown above
- # else: # this case is impossible for now.
- """
- self.DEBUG('Registering handler %s for "%s" node->%s'%(handler,jid,node), 'info')
- node,key=self._traversePath(node,jid,1)
- node[key]=handler
-
- def getDiscoHandler(self,node='',jid=''):
- """ Returns the previously registered DISCO handler
- that is resonsible for this node/jid combination.
- Used internally."""
- node,key=self._traversePath(node,jid)
- if node: return node[key]
-
- def delDiscoHandler(self,node='',jid=''):
- """ Unregisters DISCO handler that is resonsible for this
- node/jid combination. When handler is unregistered the branch
- is handled in the same way that it's parent branch from this moment.
- """
- node,key=self._traversePath(node,jid)
- if node:
- handler=node[key]
- del node[dict][node[str]]
- return handler
-
- def _DiscoveryHandler(self,conn,request):
- """ Servers DISCO iq request from the remote client.
- Automatically determines the best handler to use and calls it
- to handle the request. Used internally.
- """
- handler=self.getDiscoHandler(request.getQuerynode(),request.getTo())
- if not handler:
- self.DEBUG("No Handler for request with jid->%s node->%s ns->%s"%(request.getTo(),request.getQuerynode(),request.getQueryNS()),'error')
- conn.send(Error(request,ERR_ITEM_NOT_FOUND))
- raise NodeProcessed
- self.DEBUG("Handling request with jid->%s node->%s ns->%s"%(request.getTo(),request.getQuerynode(),request.getQueryNS()),'ok')
- rep=request.buildReply('result')
- if request.getQuerynode(): rep.setQuerynode(request.getQuerynode())
- q=rep.getTag('query')
- if request.getQueryNS()==NS_DISCO_ITEMS:
- # handler must return list: [{jid,action,node,name}]
- if isinstance(handler, dict): lst=handler['items']
- else: lst=handler(conn,request,'items')
- if lst is None:
- conn.send(Error(request,ERR_ITEM_NOT_FOUND))
- raise NodeProcessed
- for item in lst: q.addChild('item',item)
- elif request.getQueryNS()==NS_DISCO_INFO:
- if isinstance(handler, dict): dt=handler['info']
- else: dt=handler(conn,request,'info')
- if dt is None:
- conn.send(Error(request,ERR_ITEM_NOT_FOUND))
- raise NodeProcessed
- # handler must return dictionary:
- # {'ids':[{},{},{},{}], 'features':[fe,at,ur,es], 'xdata':DataForm}
- for id_ in dt['ids']: q.addChild('identity',id_)
- for feature in dt['features']: q.addChild('feature',{'var':feature})
- if 'xdata' in dt: q.addChild(node=dt['xdata'])
- conn.send(rep)
- raise NodeProcessed
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/client.py b/src/common/xmpp/client.py
index c3c0aab46..8befaec0d 100644
--- a/src/common/xmpp/client.py
+++ b/src/common/xmpp/client.py
@@ -14,311 +14,55 @@
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
-"""
+'''
Provides PlugIn class functionality to develop extentions for xmpppy.
Also provides Client and Component classes implementations as the
examples of xmpppy structures usage.
These classes can be used for simple applications "AS IS" though.
-"""
+'''
-import socket
-import debug
-Debug=debug
-Debug.DEBUGGING_IS_ON=1
-Debug.Debug.colors['socket']=debug.color_dark_gray
-Debug.Debug.colors['CONNECTproxy']=debug.color_dark_gray
-Debug.Debug.colors['nodebuilder']=debug.color_brown
-Debug.Debug.colors['client']=debug.color_cyan
-Debug.Debug.colors['component']=debug.color_cyan
-Debug.Debug.colors['dispatcher']=debug.color_green
-Debug.Debug.colors['browser']=debug.color_blue
-Debug.Debug.colors['auth']=debug.color_yellow
-Debug.Debug.colors['roster']=debug.color_magenta
-Debug.Debug.colors['ibb']=debug.color_yellow
-
-Debug.Debug.colors['down']=debug.color_brown
-Debug.Debug.colors['up']=debug.color_brown
-Debug.Debug.colors['data']=debug.color_brown
-Debug.Debug.colors['ok']=debug.color_green
-Debug.Debug.colors['warn']=debug.color_yellow
-Debug.Debug.colors['error']=debug.color_red
-Debug.Debug.colors['start']=debug.color_dark_gray
-Debug.Debug.colors['stop']=debug.color_dark_gray
-Debug.Debug.colors['sent']=debug.color_yellow
-Debug.Debug.colors['got']=debug.color_bright_cyan
-
-DBG_CLIENT='client'
-DBG_COMPONENT='component'
+import logging
+log = logging.getLogger('gajim.c.x.plugin')
class PlugIn:
- """ Common xmpppy plugins infrastructure: plugging in/out, debugging. """
- def __init__(self):
- self._exported_methods=[]
- self.DBG_LINE=self.__class__.__name__.lower()
+ ''' Common xmpppy plugins infrastructure: plugging in/out, debugging. '''
+ def __init__(self):
+ self._exported_methods=[]
- def PlugIn(self,owner):
- """ Attach to main instance and register ourself and all our staff in it. """
- self._owner=owner
- if self.DBG_LINE not in owner.debug_flags:
- owner.debug_flags.append(self.DBG_LINE)
- self.DEBUG('Plugging %s into %s'%(self,self._owner),'start')
- if self.__class__.__name__ in owner.__dict__:
- return self.DEBUG('Plugging ignored: another instance already plugged.','error')
- self._old_owners_methods=[]
- for method in self._exported_methods:
- if method.__name__ in owner.__dict__:
- self._old_owners_methods.append(owner.__dict__[method.__name__])
- owner.__dict__[method.__name__]=method
- owner.__dict__[self.__class__.__name__]=self
- if 'plugin' in self.__class__.__dict__: return self.plugin(owner)
+ def PlugIn(self,owner):
+ ''' Attach to main instance and register ourself and all our staff in it. '''
+ self._owner=owner
+ log.info('Plugging %s __INTO__ %s' % (self,self._owner))
+ if self.__class__.__name__ in owner.__dict__:
+ log.debug('Plugging ignored: another instance already plugged.')
+ return
+ self._old_owners_methods=[]
+ for method in self._exported_methods:
+ if method.__name__ in owner.__dict__:
+ self._old_owners_methods.append(owner.__dict__[method.__name__])
+ owner.__dict__[method.__name__]=method
+ if self.__class__.__name__.endswith('Dispatcher'):
+ # FIXME: I need BOSHDispatcher or XMPPDispatcher on .Dispatcher
+ # there must be a better way..
+ owner.__dict__['Dispatcher']=self
+ else:
+ owner.__dict__[self.__class__.__name__]=self
- def PlugOut(self):
- """ Unregister all our staff from main instance and detach from it. """
- self.DEBUG('Plugging %s out of %s.'%(self,self._owner),'stop')
- self._owner.debug_flags.remove(self.DBG_LINE)
- for method in self._exported_methods: del self._owner.__dict__[method.__name__]
- for method in self._old_owners_methods: self._owner.__dict__[method.__name__]=method
- del self._owner.__dict__[self.__class__.__name__]
- if 'plugout' in self.__class__.__dict__: return self.plugout()
- del self._owner
+ # following commented line will not work for classes inheriting plugin()
+ #if self.__class__.__dict__.has_key('plugin'): return self.plugin(owner)
+ if hasattr(self,'plugin'): return self.plugin(owner)
- def DEBUG(self,text,severity='info'):
- """ Feed a provided debug line to main instance's debug facility along with our ID string. """
- self._owner.DEBUG(self.DBG_LINE,text,severity)
-
-import transports,dispatcher,auth,roster
-class CommonClient:
- """ Base for Client and Component classes."""
- def __init__(self,server,port=5222,debug=['always', 'nodebuilder'],caller=None):
- """ Caches server name and (optionally) port to connect to. "debug" parameter specifies
- the debug IDs that will go into debug output. You can either specifiy an "include"
- or "exclude" list. The latter is done via adding "always" pseudo-ID to the list.
- Full list: ['nodebuilder', 'dispatcher', 'gen_auth', 'SASL_auth', 'bind', 'socket',
- 'CONNECTproxy', 'TLS', 'roster', 'browser', 'ibb'] . """
- if self.__class__.__name__=='Client': self.Namespace,self.DBG='jabber:client',DBG_CLIENT
- elif self.__class__.__name__=='Component': self.Namespace,self.DBG=dispatcher.NS_COMPONENT_ACCEPT,DBG_COMPONENT
- self.defaultNamespace=self.Namespace
- self.disconnect_handlers=[]
- self.Server=server
- self.Port=port
- # Who initiated this client
- # Used to register the EventDispatcher
- self._caller=caller
- if debug and not isinstance(debug, list): debug=['always', 'nodebuilder']
- self._DEBUG=Debug.Debug(debug)
- self.DEBUG=self._DEBUG.Show
- self.debug_flags=self._DEBUG.debug_flags
- self.debug_flags.append(self.DBG)
- self._owner=self
- self._registered_name=None
- self.RegisterDisconnectHandler(self.DisconnectHandler)
- self.connected=''
- self._component=0
-
- def RegisterDisconnectHandler(self,handler):
- """ Register handler that will be called on disconnect."""
- self.disconnect_handlers.append(handler)
-
- def UnregisterDisconnectHandler(self,handler):
- """ Unregister handler that is called on disconnect."""
- self.disconnect_handlers.remove(handler)
-
- def disconnected(self):
- """ Called on disconnection. Calls disconnect handlers and cleans things up. """
- self.connected=''
- self.DEBUG(self.DBG,'Disconnect detected','stop')
- self.disconnect_handlers.reverse()
- for i in self.disconnect_handlers: i()
- self.disconnect_handlers.reverse()
- if 'TLS' in self.__dict__: self.TLS.PlugOut()
-
- def DisconnectHandler(self):
- """ Default disconnect handler. Just raises an IOError.
- If you choosed to use this class in your production client,
- override this method or at least unregister it. """
- raise IOError('Disconnected from server.')
-
- def event(self,eventName,args={}):
- """ Default event handler. To be overriden. """
- print "Event: ",(eventName,args)
-
- def isConnected(self):
- """ Returns connection state. F.e.: None / 'tls' / 'tcp+non_sasl' . """
- return self.connected
-
- def reconnectAndReauth(self):
- """ Example of reconnection method. In fact, it can be used to batch connection and auth as well. """
- handlerssave=self.Dispatcher.dumpHandlers()
- self.Dispatcher.PlugOut()
- if 'NonSASL' in self.__dict__: self.NonSASL.PlugOut()
- if 'SASL' in self.__dict__: self.SASL.PlugOut()
- if 'TLS' in self.__dict__: self.TLS.PlugOut()
- if 'HTTPPROXYsocket' in self.__dict__: self.HTTPPROXYsocket.PlugOut()
- if 'TCPsocket' in self.__dict__: self.TCPsocket.PlugOut()
- if not self.connect(server=self._Server,proxy=self._Proxy): return
- if not self.auth(self._User,self._Password,self._Resource): return
- self.Dispatcher.restoreHandlers(handlerssave)
- return self.connected
-
- def get_peerhost(self):
- ''' get the ip address of the account, from which is made connection
- to the server , (e.g. me).
- We will create listening socket on the same ip '''
- if hasattr(self, 'Connection'):
- return self.Connection._sock.getsockname()
-
- def connect(self,server=None,proxy=None,ssl=None,use_srv=None):
- """ Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream.
- Returns None or 'tcp' or 'tls', depending on the result."""
- if not server: server=(self.Server,self.Port)
- if proxy: socket=transports.HTTPPROXYsocket(proxy,server,use_srv)
- else: socket=transports.TCPsocket(server,use_srv)
- connected=socket.PlugIn(self)
- if not connected:
- socket.PlugOut()
- return
- self._Server,self._Proxy=server,proxy
- self.connected='tcp'
- if (ssl is None and self.Connection.getPort() in (5223, 443)) or ssl:
- try: # FIXME. This should be done in transports.py
- transports.TLS().PlugIn(self,now=1)
- self.connected='ssl'
- except socket.sslerror:
- return
- dispatcher.Dispatcher().PlugIn(self)
- while self.Dispatcher.Stream._document_attrs is None:
- if not self.Process(1): return
- if 'version' in self.Dispatcher.Stream._document_attrs and self.Dispatcher.Stream._document_attrs['version']=='1.0':
- while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented
- return self.connected
-
-class Client(CommonClient):
- """ Example client class, based on CommonClient. """
- def connect(self,server=None,proxy=None,secure=None,use_srv=True):
- """ Connect to jabber server. If you want to specify different ip/port to connect to you can
- pass it as tuple as first parameter. If there is HTTP proxy between you and server
- specify it's address and credentials (if needed) in the second argument.
- If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443)
- If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1.
- If you want to disable tls/ssl support completely, set it to 0.
- Example: connect(('192.168.5.5',5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'})
- Returns '' or 'tcp' or 'tls', depending on the result."""
- if not CommonClient.connect(self,server,proxy,secure,use_srv) or secure!=None and not secure: return self.connected
- transports.TLS().PlugIn(self)
- if 'version' not in self.Dispatcher.Stream._document_attrs or not self.Dispatcher.Stream._document_attrs['version']=='1.0': return self.connected
- while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented
- if not self.Dispatcher.Stream.features.getTag('starttls'): return self.connected # TLS not supported by server
- while not self.TLS.starttls and self.Process(): pass
- if not hasattr(self, 'TLS') or self.TLS.starttls!='success': self.event('tls_failed'); return self.connected
- self.connected='tls'
- return self.connected
-
- def auth(self,user,password,resource='',sasl=1):
- """ Authenticate connnection and bind resource. If resource is not provided
- random one or library name used. """
- self._User,self._Password,self._Resource=user,password,resource
- while not self.Dispatcher.Stream._document_attrs and self.Process(): pass
- if 'version' in self.Dispatcher.Stream._document_attrs and self.Dispatcher.Stream._document_attrs['version']=='1.0':
- while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented
- if sasl: auth.SASL(user,password).PlugIn(self)
- if not sasl or self.SASL.startsasl=='not-supported':
- if not resource: resource='xmpppy'
- if auth.NonSASL(user,password,resource).PlugIn(self):
- self.connected+='+old_auth'
- return 'old_auth'
- return
- self.SASL.auth()
- while self.SASL.startsasl=='in-process' and self.Process(): pass
- if self.SASL.startsasl=='success':
- auth.Bind().PlugIn(self)
- while self.Bind.bound is None and self.Process(): pass
- if self.Bind.Bind(resource):
- self.connected+='+sasl'
- return 'sasl'
-
- def initRoster(self):
- """ Plug in the roster. """
- if 'Roster' not in self.__dict__: roster.Roster().PlugIn(self)
-
- def getRoster(self):
- """ Return the Roster instance, previously plugging it in and
- requesting roster from server if needed. """
- self.initRoster()
- return self.Roster.getRoster()
-
- def sendInitPresence(self,requestRoster=1):
- """ Send roster request and initial .
- You can disable the first by setting requestRoster argument to 0. """
- self.sendPresence(requestRoster=requestRoster)
-
- def sendPresence(self,jid=None,typ=None,requestRoster=0):
- """ Send some specific presence state.
- Can also request roster from server if according agrument is set."""
- if requestRoster: roster.Roster().PlugIn(self)
- self.send(dispatcher.Presence(to=jid, typ=typ))
-
-class Component(CommonClient):
- """ Component class. The only difference from CommonClient is ability to perform component authentication. """
- def __init__(self,server,port=5347,typ=None,debug=['always', 'nodebuilder'],domains=None,component=0):
- """ Init function for Components.
- As components use a different auth mechanism which includes the namespace of the component.
- Jabberd1.4 and Ejabberd use the default namespace then for all client messages.
- Jabberd2 uses jabber:client.
- 'server' argument is a server name that you are connecting to (f.e. "localhost").
- 'port' can be specified if 'server' resolves to correct IP. If it is not then you'll need to specify IP
- and port while calling "connect()"."""
- CommonClient.__init__(self,server,port=port,debug=debug)
- self.typ=typ
- self.component=component
- if domains:
- self.domains=domains
- else:
- self.domains=[server]
-
- def connect(self,server=None,proxy=None):
- """ This will connect to the server, and if the features tag is found then set
- the namespace to be jabber:client as that is required for jabberd2.
- 'server' and 'proxy' arguments have the same meaning as in xmpp.Client.connect() """
- if self.component:
- self.Namespace=auth.NS_COMPONENT_1
- self.Server=server[0]
- CommonClient.connect(self,server=server,proxy=proxy)
- if self.connected and (self.typ=='jabberd2' or not self.typ and self.Dispatcher.Stream.features is not None):
- self.defaultNamespace=auth.NS_CLIENT
- self.Dispatcher.RegisterNamespace(self.defaultNamespace)
- self.Dispatcher.RegisterProtocol('iq',dispatcher.Iq)
- self.Dispatcher.RegisterProtocol('message',dispatcher.Message)
- self.Dispatcher.RegisterProtocol('presence',dispatcher.Presence)
- return self.connected
-
- def auth(self,name,password,dup=None,sasl=0):
- """ Authenticate component "name" with password "password"."""
- self._User,self._Password,self._Resource=name,password,''
- try:
- if self.component: sasl=1
- if sasl: auth.SASL(name,password).PlugIn(self)
- if not sasl or self.SASL.startsasl=='not-supported':
- if auth.NonSASL(name,password,'').PlugIn(self):
- self.connected+='+old_auth'
- return 'old_auth'
- return
- self.SASL.auth()
- while self.SASL.startsasl=='in-process' and self.Process(): pass
- if self.SASL.startsasl=='success':
- if self.component:
- self._component=self.component
- for domain in self.domains:
- auth.ComponentBind().PlugIn(self)
- while self.ComponentBind.bound is None: self.Process()
- if (not self.ComponentBind.Bind(domain)):
- self.ComponentBind.PlugOut()
- return
- self.ComponentBind.PlugOut()
- self.connected+='+sasl'
- return 'sasl'
- else:
- raise auth.NotAuthorized(self.SASL.startsasl)
- except Exception:
- self.DEBUG(self.DBG,"Failed to authenticate %s"%name,'error')
+ def PlugOut(self):
+ ''' Unregister all our staff from main instance and detach from it. '''
+ log.info('Plugging %s __OUT__ of %s.' % (self, self._owner))
+ for method in self._exported_methods: del self._owner.__dict__[method.__name__]
+ for method in self._old_owners_methods: self._owner.__dict__[method.__name__]=method
+ if self.__class__.__name__.endswith('Dispatcher'):
+ del self._owner.__dict__['Dispatcher']
+ else:
+ del self._owner.__dict__[self.__class__.__name__]
+ #if self.__class__.__dict__.has_key('plugout'): return self.plugout()
+ if hasattr(self,'plugout'): return self.plugout()
+ del self._owner
# vim: se ts=3:
diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py
index d9a4a8bef..31ac6795b 100644
--- a/src/common/xmpp/client_nb.py
+++ b/src/common/xmpp/client_nb.py
@@ -1,8 +1,8 @@
## client_nb.py
-## based on client.py
+## based on client.py
##
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-## modified by Dimitur Kirov
+## modified by Dimitur Kirov
##
## 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
@@ -16,64 +16,66 @@
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
-'''
-Provides PlugIn class functionality to develop extentions for xmpppy.
-Also provides Client and Component classes implementations as the
-examples of xmpppy structures usage.
-These classes can be used for simple applications "AS IS" though.
-'''
+import socket
-import transports_nb, dispatcher_nb, auth_nb, roster_nb
+import transports_nb, dispatcher_nb, auth_nb, roster_nb, protocol, bosh
from client import *
-class NBCommonClient(CommonClient):
- ''' Base for Client and Component classes.'''
- def __init__(self, server, port=5222, debug=['always', 'nodebuilder'], caller=None,
- on_connect=None, on_proxy_failure=None, on_connect_failure=None):
- ''' Caches server name and (optionally) port to connect to. "debug" parameter specifies
- the debug IDs that will go into debug output. You can either specifiy an "include"
- or "exclude" list. The latter is done via adding "always" pseudo-ID to the list.
- Full list: ['nodebuilder', 'dispatcher', 'gen_auth', 'SASL_auth', 'bind', 'socket',
- 'CONNECTproxy', 'TLS', 'roster', 'browser', 'ibb'] . '''
+from protocol import NS_TLS
- if isinstance(self, NonBlockingClient):
- self.Namespace, self.DBG = 'jabber:client', DBG_CLIENT
- elif isinstance(self, NBCommonClient):
- self.Namespace, self.DBG = dispatcher_nb.NS_COMPONENT_ACCEPT, DBG_COMPONENT
+import logging
+log = logging.getLogger('gajim.c.x.client_nb')
+
+class NonBlockingClient:
+ '''
+ Client class is XMPP connection mountpoint. Objects for authentication,
+ network communication, roster, xml parsing ... are plugged to client object.
+ Client implements the abstract behavior - mostly negotioation and callbacks
+ handling, whereas underlying modules take care of feature-specific logic.
+ '''
+ def __init__(self, domain, idlequeue, caller=None):
+ '''
+ Caches connection data:
+ :param domain: domain - for to: attribute (from account info)
+ :param idlequeue: processing idlequeue
+ :param caller: calling object - it has to implement methods _event_dispatcher
+ which is called from dispatcher instance
+ '''
+ self.Namespace = protocol.NS_CLIENT
self.defaultNamespace = self.Namespace
- self.disconnect_handlers = []
- self.Server = server
- self.Port = port
- # Who initiated this client
- # Used to register the EventDispatcher
+ self.idlequeue = idlequeue
+ self.disconnect_handlers = []
+
+ self.Server = domain
+
+ # caller is who initiated this client, it is ineeded to register the EventDispatcher
self._caller = caller
- if debug and not isinstance(debug, list):
- debug = ['always', 'nodebuilder']
- self._DEBUG = Debug.Debug(debug)
- self.DEBUG = self._DEBUG.Show
- self.debug_flags = self._DEBUG.debug_flags
- self.debug_flags.append(self.DBG)
self._owner = self
self._registered_name = None
self.connected = ''
- self._component=0
- self.idlequeue = None
self.socket = None
- self.on_connect = on_connect
- self.on_proxy_failure = on_proxy_failure
- self.on_connect_failure = on_connect_failure
+ self.on_connect = None
+ self.on_proxy_failure = None
+ self.on_connect_failure = None
+ self.proxy = None
+ self.got_features = False
+ self.stream_started = False
+ self.disconnecting = False
+ self.protocol_type = 'XMPP'
- def set_idlequeue(self, idlequeue):
- self.idlequeue = idlequeue
+ def disconnect(self, message=''):
+ '''
+ Called on disconnection - disconnect callback is picked based on state of the
+ client.
+ '''
+
+ # to avoid recursive calls
+ if self.disconnecting: return
+
+ log.info('Disconnecting NBClient: %s' % message)
- def disconnected(self):
- ''' Called on disconnection. Calls disconnect handlers and cleans things up. '''
- self.connected=''
- self.DEBUG(self.DBG,'Disconnect detected','stop')
- for i in reversed(self.disconnect_handlers):
- i()
if 'NonBlockingRoster' in self.__dict__:
self.NonBlockingRoster.PlugOut()
if 'NonBlockingBind' in self.__dict__:
@@ -82,172 +84,319 @@ class NBCommonClient(CommonClient):
self.NonBlockingNonSASL.PlugOut()
if 'SASL' in self.__dict__:
self.SASL.PlugOut()
- if 'NonBlockingTLS' in self.__dict__:
- self.NonBlockingTLS.PlugOut()
- if 'NBHTTPPROXYsocket' in self.__dict__:
- self.NBHTTPPROXYsocket.PlugOut()
- if 'NBSOCKS5PROXYsocket' in self.__dict__:
- self.NBSOCKS5PROXYsocket.PlugOut()
- if 'NonBlockingTcp' in self.__dict__:
- self.NonBlockingTcp.PlugOut()
+ if 'NonBlockingTCP' in self.__dict__:
+ self.NonBlockingTCP.PlugOut()
+ if 'NonBlockingHTTP' in self.__dict__:
+ self.NonBlockingHTTP.PlugOut()
+ if 'NonBlockingBOSH' in self.__dict__:
+ self.NonBlockingBOSH.PlugOut()
- def reconnectAndReauth(self):
- ''' Just disconnect. We do reconnecting in connection.py '''
- self.disconnect()
- return ''
+ connected = self.connected
+ stream_started = self.stream_started
- def connect(self,server=None,proxy=None, ssl=None, on_stream_start = None):
- ''' Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream. '''
- if not server:
- server = (self.Server, self.Port)
- self._Server, self._Proxy, self._Ssl = server , proxy, ssl
- self.on_stream_start = on_stream_start
- if proxy:
- if 'type' in proxy:
- type_ = proxy['type']
- if type_ == 'socks5':
- self.socket = transports_nb.NBSOCKS5PROXYsocket(
- self._on_connected, self._on_proxy_failure,
- self._on_connected_failure, proxy, server)
- elif type_ == 'http':
- self.socket = transports_nb.NBHTTPPROXYsocket(self._on_connected,
- self._on_proxy_failure, self._on_connected_failure, proxy,
- server)
+ self.connected = ''
+ self.stream_started = False
+
+ self.disconnecting = True
+
+ log.debug('Client disconnected..')
+ if connected == '':
+ # if we're disconnecting before connection to XMPP sever is opened, we don't
+ # call disconnect handlers but on_connect_failure callback
+ if self.proxy:
+ # with proxy, we have different failure callback
+ log.debug('calling on_proxy_failure cb')
+ self.on_proxy_failure(reason=message)
else:
- self.socket = transports_nb.NBHTTPPROXYsocket(self._on_connected,
- self._on_proxy_failure, self._on_connected_failure, proxy,
- server)
+ log.debug('ccalling on_connect_failure cb')
+ self.on_connect_failure()
else:
- self.connected = 'tcp'
- self.socket = transports_nb.NonBlockingTcp(self._on_connected,
- self._on_connected_failure, server)
- self.socket.PlugIn(self)
- return True
+ # we are connected to XMPP server
+ if not stream_started:
+ # if error occur before XML stream was opened, e.g. no response on init
+ # request, we call the on_connect_failure callback because proper
+ # connection is not estabilished yet and it's not a proxy issue
+ log.debug('calling on_connect_failure cb')
+ self.on_connect_failure()
+ else:
+ # with open connection, we are calling the disconnect handlers
+ for i in reversed(self.disconnect_handlers):
+ log.debug('Calling disconnect handler %s' % i)
+ i()
+ self.disconnecting = False
- def get_attrs(self, on_stream_start):
- self.on_stream_start = on_stream_start
- self.onreceive(self._on_receive_document_attrs)
- def _on_proxy_failure(self, reason):
- if self.on_proxy_failure:
- self.on_proxy_failure(reason)
+ def connect(self, on_connect, on_connect_failure, hostname=None, port=5222,
+ on_proxy_failure=None, proxy=None, secure_tuple=(None, None, None)):
+ '''
+ Open XMPP connection (open XML streams in both directions).
+ :param on_connect: called after stream is successfully opened
+ :param on_connect_failure: called when error occures during connection
+ :param hostname: hostname of XMPP server from SRV request
+ :param port: port number of XMPP server
+ :param on_proxy_failure: called if error occurres during TCP connection to
+ proxy server or during proxy connecting process
+ :param proxy: dictionary with proxy data. It should contain at least values
+ for keys 'host' and 'port' - connection details for proxy server and
+ optionally keys 'user' and 'pass' as proxy credentials
+ :param secure_tuple: tuple of (desired connection type, cacerts and mycerts)
+ connection type can be 'ssl' - TLS estabilished after TCP connection,
+ 'tls' - TLS estabilished after negotiation with starttls, or 'plain'.
+ cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more details
+ '''
- def _on_connected_failure(self, retry = None):
- if self.socket:
- self.socket.disconnect()
- if self.on_connect_failure:
- self.on_connect_failure(retry)
-
- def _on_connected(self):
- # FIXME: why was this needed? Please note that we're working
- # in nonblocking mode, and this handler is actually called
- # as soon as connection is initiated, NOT when connection
- # succeeds, as the name suggests.
- # # connect succeeded, so no need of this callback anymore
- # self.on_connect_failure = None
- self.connected = 'tcp'
- if self._Ssl:
- transports_nb.NonBlockingTLS().PlugIn(self, now=1)
- if not self.Connection: # ssl error, stream is closed
- return
- self.connected = 'ssl'
- self.onreceive(self._on_receive_document_attrs)
- dispatcher_nb.Dispatcher().PlugIn(self)
-
- def _on_receive_document_attrs(self, data):
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not hasattr(self, 'Dispatcher') or \
- self.Dispatcher.Stream._document_attrs is None:
- return
- self.onreceive(None)
- if 'version' in self.Dispatcher.Stream._document_attrs and \
- self.Dispatcher.Stream._document_attrs['version'] == '1.0':
- self.onreceive(self._on_receive_stream_features)
- return
- if self.on_stream_start:
- self.on_stream_start()
- self.on_stream_start = None
- return True
-
- def _on_receive_stream_features(self, data):
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not self.Dispatcher.Stream.features:
- return
- # pass # If we get version 1.0 stream the features tag MUST BE presented
- self.onreceive(None)
- if self.on_stream_start:
- self.on_stream_start()
- self.on_stream_start = None
- return True
-
-class NonBlockingClient(NBCommonClient):
- ''' Example client class, based on CommonClient. '''
- def connect(self,server=None,proxy=None,secure=None,use_srv=True):
- ''' Connect to jabber server. If you want to specify different ip/port to connect to you can
- pass it as tuple as first parameter. If there is HTTP proxy between you and server
- specify it's address and credentials (if needed) in the second argument.
- If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443)
- If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1.
- If you want to disable tls/ssl support completely, set it to 0.
- Example: connect(('192.168.5.5',5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'})
- Returns '' or 'tcp' or 'tls', depending on the result.'''
- self.__secure = secure
+ self.on_connect = on_connect
+ self.on_connect_failure=on_connect_failure
+ self.on_proxy_failure = on_proxy_failure
+ self.secure, self.cacerts, self.mycerts = secure_tuple
self.Connection = None
- NBCommonClient.connect(self, server = server, proxy = proxy, ssl = secure,
- on_stream_start = self._on_tcp_stream_start)
- return self.connected
+ self.Port = port
+ self.proxy = proxy
+
+ if hostname:
+ xmpp_hostname = hostname
+ else:
+ xmpp_hostname = self.Server
+
+ estabilish_tls = self.secure == 'ssl'
+ certs = (self.cacerts, self.mycerts)
+
+ proxy_dict = {}
+ tcp_host=xmpp_hostname
+ tcp_port=self.Port
+
+ if proxy:
+ # with proxies, client connects to proxy instead of directly to
+ # XMPP server ((hostname, port))
+ # tcp_host is hostname of machine used for socket connection
+ # (DNS request will be done for proxy or BOSH CM hostname)
+ tcp_host, tcp_port, proxy_user, proxy_pass = \
+ transports_nb.get_proxy_data_from_dict(proxy)
+
+
+ if proxy['type'] == 'bosh':
+ self.socket = bosh.NonBlockingBOSH(
+ on_disconnect = self.disconnect,
+ raise_event = self.raise_event,
+ idlequeue = self.idlequeue,
+ estabilish_tls = estabilish_tls,
+ certs = certs,
+ proxy_creds = (proxy_user, proxy_pass),
+ xmpp_server = (xmpp_hostname, self.Port),
+ domain = self.Server,
+ bosh_dict = proxy)
+ self.protocol_type = 'BOSH'
+ self.wait_for_restart_response = proxy['bosh_wait_for_restart_response']
+
+ else:
+ proxy_dict['type'] = proxy['type']
+ proxy_dict['xmpp_server'] = (xmpp_hostname, self.Port)
+ proxy_dict['credentials'] = (proxy_user, proxy_pass)
+
+ if not proxy or proxy['type'] != 'bosh':
+ self.socket = transports_nb.NonBlockingTCP(
+ on_disconnect = self.disconnect,
+ raise_event = self.raise_event,
+ idlequeue = self.idlequeue,
+ estabilish_tls = estabilish_tls,
+ certs = certs,
+ proxy_dict = proxy_dict)
+
+ self.socket.PlugIn(self)
+
+ self._resolve_hostname(
+ hostname=tcp_host,
+ port=tcp_port,
+ on_success=self._try_next_ip)
+
+ def _resolve_hostname(self, hostname, port, on_success):
+ ''' wrapper for getaddinfo call. FIXME: getaddinfo blocks'''
+ try:
+ self.ip_addresses = socket.getaddrinfo(hostname,port,
+ socket.AF_UNSPEC,socket.SOCK_STREAM)
+ except socket.gaierror, (errnum, errstr):
+ self.disconnect(message='Lookup failure for %s:%s, hostname: %s - %s' %
+ (self.Server, self.Port, hostname, errstr))
+ else:
+ on_success()
+
+ def _try_next_ip(self, err_message=None):
+ '''iterates over IP addresses from getaddrinfo'''
+ if err_message:
+ log.debug('While looping over DNS A records: %s' % err_message)
+ if self.ip_addresses == []:
+ msg = 'Run out of hosts for name %s:%s.' % (self.Server, self.Port)
+ msg = msg + ' Error for last IP: %s' % err_message
+ self.disconnect(msg)
+ else:
+ self.current_ip = self.ip_addresses.pop(0)
+ self.socket.connect(
+ conn_5tuple=self.current_ip,
+ on_connect=lambda: self._xmpp_connect(socket_type='plain'),
+ on_connect_failure=self._try_next_ip)
- def _is_connected(self):
+ def incoming_stream_version(self):
+ ''' gets version of xml stream'''
+ if self.Dispatcher.Stream._document_attrs.has_key('version'):
+ return self.Dispatcher.Stream._document_attrs['version']
+ else:
+ return None
+
+ def _xmpp_connect(self, socket_type):
+ '''
+ Starts XMPP connecting process - opens the XML stream. Is called after TCP
+ connection is estabilished or after switch to TLS when successfully
+ negotiated with .
+ '''
+ if socket_type == 'plain' and self.Connection.ssl_lib:
+ socket_type = 'ssl'
+ self.connected = socket_type
+ self._xmpp_connect_machine()
+
+
+ def _xmpp_connect_machine(self, mode=None, data=None):
+ '''
+ Finite automaton taking care of stream opening and features tag
+ handling. Calls _on_stream_start when stream is started, and disconnect()
+ on failure.
+ '''
+ log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' % (mode,str(data)[:20] ))
+
+ def on_next_receive(mode):
+ log.info('setting %s on next receive' % mode)
+ if mode is None:
+ self.onreceive(None)
+ else:
+ self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data))
+
+ if not mode:
+ # starting state
+ if self.__dict__.has_key('Dispatcher'):
+ self.Dispatcher.PlugOut()
+ self.got_features = False
+ d = dispatcher_nb.Dispatcher().PlugIn(self)
+ on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES')
+
+ elif mode == 'FAILURE':
+ self.disconnect('During XMPP connect: %s' % data)
+
+ elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES':
+ if data:
+ self.Dispatcher.ProcessNonBlocking(data)
+ if not hasattr(self, 'Dispatcher') or \
+ self.Dispatcher.Stream._document_attrs is None:
+ self._xmpp_connect_machine(
+ mode='FAILURE',
+ data='Error on stream open')
+ if self.incoming_stream_version() == '1.0':
+ if not self.got_features:
+ on_next_receive('RECEIVE_STREAM_FEATURES')
+ else:
+ log.info('got STREAM FEATURES in first recv')
+ self._xmpp_connect_machine(mode='STREAM_STARTED')
+
+ else:
+ log.info('incoming stream version less than 1.0')
+ self._xmpp_connect_machine(mode='STREAM_STARTED')
+
+ elif mode == 'RECEIVE_STREAM_FEATURES':
+ if data:
+ # sometimes are received together with document
+ # attributes and sometimes on next receive...
+ self.Dispatcher.ProcessNonBlocking(data)
+ if not self.got_features:
+ self._xmpp_connect_machine(
+ mode='FAILURE',
+ data='Missing in 1.0 stream')
+ else:
+ log.info('got STREAM FEATURES in second recv')
+ self._xmpp_connect_machine(mode='STREAM_STARTED')
+
+ elif mode == 'STREAM_STARTED':
+ self._on_stream_start()
+
+ def _on_stream_start(self):
+ '''
+ Called after XMPP stream is opened.
+ TLS negotiation may follow after esabilishing a stream.
+ '''
+ self.stream_started = True
self.onreceive(None)
- if self.on_connect:
- self.on_connect(self, self.connected)
- self.on_connect_failure = None
- self.on_connect = None
+ if self.connected == 'plain':
+ if self.secure == 'plain':
+ # if we want plain connection, we're done now
+ self._on_connect()
+ return
+ if not self.Dispatcher.Stream.features.getTag('starttls'):
+ # if server doesn't advertise TLS in init response, we can't do more
+ log.warn('While connecting with type = "tls": TLS unsupported by remote server')
+ self._on_connect()
+ return
+ if self.incoming_stream_version() != '1.0':
+ # if stream version is less than 1.0, we can't do more
+ log.warn('While connecting with type = "tls": stream version is less than 1.0')
+ self._on_connect()
+ return
+ # otherwise start TLS negotioation
+ self.stream_started = False
+ log.info("TLS supported by remote server. Requesting TLS start.")
+ self._tls_negotiation_handler()
+ elif self.connected in ['ssl', 'tls']:
+ self._on_connect()
- def _on_tcp_stream_start(self):
- if not self.connected or self.__secure is not None and not self.__secure:
- self._is_connected()
- return True
- self.isplugged = True
- self.onreceive(None)
- transports_nb.NonBlockingTLS().PlugIn(self)
- if not self.Connection: # ssl error, stream is closed
- return True
- if 'version' not in self.Dispatcher.Stream._document_attrs or \
- not self.Dispatcher.Stream._document_attrs['version']=='1.0':
- self._is_connected()
- return
- if not self.Dispatcher.Stream.features.getTag('starttls'):
- self._is_connected()
- return
- self.onreceive(self._on_receive_starttls)
+ def _tls_negotiation_handler(self, con=None, tag=None):
+ ''' takes care of TLS negotioation with '''
+ log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag)
+ if not con and not tag:
+ # starting state when we send the
+ self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler,
+ xmlns=NS_TLS)
+ self.RegisterHandlerOnce('failure', self._tls_negotiation_handler,
+ xmlns=NS_TLS)
+ self.send('' % NS_TLS)
+ else:
+ # we got or
+ if tag.getNamespace() <> NS_TLS:
+ self.disconnect('Unknown namespace: %s' % tag.getNamespace())
+ return
+ tagname = tag.getName()
+ if tagname == 'failure':
+ self.disconnect('TLS received: %s' % tag)
+ return
+ log.info('Got starttls proceed response. Switching to TLS/SSL...')
+ # following call wouldn't work for BOSH transport but it doesn't matter
+ # because negotiation with BOSH is forbidden
+ self.Connection.tls_init(
+ on_succ = lambda: self._xmpp_connect(socket_type='tls'),
+ on_fail = lambda: self.disconnect('error while etabilishing TLS'))
- def _on_receive_starttls(self, data):
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not self.NonBlockingTLS.starttls:
- return
+ def _on_connect(self):
+ ''' preceeds call of on_connect callback '''
self.onreceive(None)
- if not hasattr(self, 'NonBlockingTLS') or self.NonBlockingTLS.starttls != 'success':
- self.event('tls_failed')
- self._is_connected()
- return
- self.connected = 'tls'
- self.onreceive(None)
- self._is_connected()
- return True
+ self.on_connect(self, self.connected)
+ def raise_event(self, event_type, data):
+ '''
+ raises event to connection instance - DATA_SENT and DATA_RECIVED events are
+ used in XML console to show XMPP traffic
+ '''
+ log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type,data))
+ if hasattr(self, 'Dispatcher'):
+ self.Dispatcher.Event('', event_type, data)
+
+ # follows code for authentication, resource bind, session and roster download
+ #
def auth(self, user, password, resource = '', sasl = 1, on_auth = None):
- ''' Authenticate connnection and bind resource. If resource is not provided
- random one or library name used. '''
+ '''
+ Authenticate connnection and bind resource. If resource is not provided
+ random one or library name used.
+ '''
self._User, self._Password, self._Resource, self._sasl = user, password, resource, sasl
self.on_auth = on_auth
- self.get_attrs(self._on_doc_attrs)
+ self._on_doc_attrs()
return
-
+
def _on_old_auth(self, res):
if res:
self.connected += '+old_auth'
@@ -263,14 +412,13 @@ class NonBlockingClient(NBCommonClient):
self._Resource = 'xmpppy'
auth_nb.NonBlockingNonSASL(self._User, self._Password, self._Resource, self._on_old_auth).PlugIn(self)
return
- self.onreceive(self._on_start_sasl)
self.SASL.auth()
return True
-
+
def _on_start_sasl(self, data=None):
if data:
self.Dispatcher.ProcessNonBlocking(data)
- if 'SASL' not in self.__dict__:
+ if not self.__dict__.has_key('SASL'):
# SASL is pluged out, possible disconnect
return
if self.SASL.startsasl == 'in-process':
@@ -280,21 +428,19 @@ class NonBlockingClient(NBCommonClient):
# wrong user/pass, stop auth
self.connected = None
self._on_sasl_auth(None)
- self.SASL.PlugOut()
elif self.SASL.startsasl == 'success':
auth_nb.NonBlockingBind().PlugIn(self)
self.onreceive(self._on_auth_bind)
return True
-
+
def _on_auth_bind(self, data):
if data:
self.Dispatcher.ProcessNonBlocking(data)
- if not hasattr(self, 'NonBlockingBind') or self.NonBlockingBind.bound is \
- None:
+ if self.NonBlockingBind.bound is None:
return
self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth)
return True
-
+
def _on_sasl_auth(self, res):
self.onreceive(None)
if res:
@@ -303,15 +449,16 @@ class NonBlockingClient(NBCommonClient):
else:
self.on_auth(self, None)
+
def initRoster(self):
''' Plug in the roster. '''
- if 'NonBlockingRoster' not in self.__dict__:
+ if not self.__dict__.has_key('NonBlockingRoster'):
roster_nb.NonBlockingRoster().PlugIn(self)
def getRoster(self, on_ready = None):
''' Return the Roster instance, previously plugging it in and
requesting roster from server if needed. '''
- if 'NonBlockingRoster' in self.__dict__:
+ if self.__dict__.has_key('NonBlockingRoster'):
return self.NonBlockingRoster.getRoster(on_ready)
return None
@@ -321,89 +468,35 @@ class NonBlockingClient(NBCommonClient):
if requestRoster: roster_nb.NonBlockingRoster().PlugIn(self)
self.send(dispatcher_nb.Presence(to=jid, typ=typ))
-class Component(NBCommonClient):
- ''' Component class. The only difference from CommonClient is ability to perform component authentication. '''
- def __init__(self, server, port=5347, typ=None, debug=['always', 'nodebuilder'],
- domains=None, component=0, on_connect = None, on_connect_failure = None):
- ''' Init function for Components.
- As components use a different auth mechanism which includes the namespace of the component.
- Jabberd1.4 and Ejabberd use the default namespace then for all client messages.
- Jabberd2 uses jabber:client.
- 'server' argument is a server name that you are connecting to (f.e. "localhost").
- 'port' can be specified if 'server' resolves to correct IP. If it is not then you'll need to specify IP
- and port while calling "connect()".'''
- NBCommonClient.__init__(self, server, port=port, debug=debug)
- self.typ = typ
- self.component=component
- if domains:
- self.domains=domains
- else:
- self.domains=[server]
- self.on_connect_component = on_connect
- self.on_connect_failure = on_connect_failure
+ # following methods are moved from blocking client class from xmpppy:
+ def RegisterDisconnectHandler(self,handler):
+ ''' Register handler that will be called on disconnect.'''
+ self.disconnect_handlers.append(handler)
- def connect(self, server=None, proxy=None):
- ''' This will connect to the server, and if the features tag is found then set
- the namespace to be jabber:client as that is required for jabberd2.
- 'server' and 'proxy' arguments have the same meaning as in xmpp.Client.connect() '''
- if self.component:
- self.Namespace=auth.NS_COMPONENT_1
- self.Server=server[0]
- NBCommonClient.connect(self, server=server, proxy=proxy,
- on_connect = self._on_connect, on_connect_failure = self.on_connect_failure)
+ def UnregisterDisconnectHandler(self,handler):
+ ''' Unregister handler that is called on disconnect.'''
+ self.disconnect_handlers.remove(handler)
- def _on_connect(self):
- if self.typ=='jabberd2' or not self.typ and self.Dispatcher.Stream.features is not None:
- self.defaultNamespace=auth.NS_CLIENT
- self.Dispatcher.RegisterNamespace(self.defaultNamespace)
- self.Dispatcher.RegisterProtocol('iq',dispatcher.Iq)
- self.Dispatcher.RegisterProtocol('message',dispatcher_nb.Message)
- self.Dispatcher.RegisterProtocol('presence',dispatcher_nb.Presence)
- self.on_connect(self.connected)
+ def DisconnectHandler(self):
+ '''
+ Default disconnect handler. Just raises an IOError. If you choosed to use
+ this class in your production client, override this method or at least
+ unregister it.
+ '''
+ raise IOError('Disconnected from server.')
- def auth(self, name, password, dup=None, sasl=0):
- ''' Authenticate component "name" with password "password".'''
- self._User, self._Password, self._Resource=name, password,''
- try:
- if self.component:
- sasl=1
- if sasl:
- auth.SASL(name,password).PlugIn(self)
- if not sasl or self.SASL.startsasl=='not-supported':
- if auth.NonSASL(name,password,'').PlugIn(self):
- self.connected+='+old_auth'
- return 'old_auth'
- return
- self.SASL.auth()
- self.onreceive(self._on_auth_component)
- except Exception:
- self.DEBUG(self.DBG,"Failed to authenticate %s" % name,'error')
-
- def _on_auth_component(self, data):
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if self.SASL.startsasl == 'in-process':
- return
- if self.SASL.startsasl =='success':
- if self.component:
- self._component = self.component
- auth.NBComponentBind().PlugIn(self)
- self.onreceive(_on_component_bind)
- self.connected += '+sasl'
- else:
- raise auth.NotAuthorized(self.SASL.startsasl)
-
- def _on_component_bind(self, data):
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if self.NBComponentBind.bound is None:
- return
-
- for domain in self.domains:
- self.NBComponentBind.Bind(domain, _on_component_bound)
-
- def _on_component_bound(self, resp):
- self.NBComponentBind.PlugOut()
+ def get_connect_type(self):
+ ''' Returns connection state. F.e.: None / 'tls' / 'plain+non_sasl'. '''
+ return self.connected
+ def get_peerhost(self):
+ '''
+ Gets the ip address of the account, from which is made connection to the
+ server , (e.g. IP and port of gajim's socket. We will create listening socket
+ on the same ip
+ '''
+ # FIXME: tuple (ip, port) is expected (and checked for) but port num is
+ # useless
+ return self.socket.peerhost
# vim: se ts=3:
diff --git a/src/common/xmpp/commands.py b/src/common/xmpp/commands.py
deleted file mode 100644
index 6feee02f0..000000000
--- a/src/common/xmpp/commands.py
+++ /dev/null
@@ -1,336 +0,0 @@
-## $Id: commands.py,v 1.11 2005/11/30 17:03:11 normanr Exp $
-
-## Ad-Hoc Command manager
-## Mike Albon (c) 5th January 2005
-
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-
-"""This module is a ad-hoc command processor for xmpppy. It uses the plug-in mechanism like most of the core library. It depends on a DISCO browser manager.
-
-There are 3 classes here, a command processor Commands like the Browser, and a command template plugin Command, and an example command.
-
-To use this module:
-
- Instansiate the module with the parent transport and disco browser manager as parameters.
- 'Plug in' commands using the command template.
- The command feature must be added to existing disco replies where neccessary.
-
-What it supplies:
-
- Automatic command registration with the disco browser manager.
- Automatic listing of commands in the public command list.
- A means of handling requests, by redirection though the command manager.
-"""
-
-import math
-from protocol import *
-from client import PlugIn
-
-DBG_COMMANDS = 'commands'
-
-class Commands(PlugIn):
- """Commands is an ancestor of PlugIn and can be attached to any session.
-
- The commands class provides a lookup and browse mechnism. It follows the same priciple of the Browser class, for Service Discovery to provide the list of commands, it adds the 'list' disco type to your existing disco handler function.
-
- How it works:
- The commands are added into the existing Browser on the correct nodes. When the command list is built the supplied discovery handler function needs to have a 'list' option in type. This then gets enumerated, all results returned as None are ignored.
- The command executed is then called using it's Execute method. All session management is handled by the command itself.
- """
- def __init__(self, browser):
- """Initialises class and sets up local variables"""
- PlugIn.__init__(self)
- self.DBG_LINE = DBG_COMMANDS
- self._exported_methods=[]
- self._handlers={'':{}}
- self._browser = browser
-
- def plugin(self, owner):
- """Makes handlers within the session"""
- # Plug into the session and the disco manager
- # We only need get and set, results are not needed by a service provider, only a service user.
- owner.RegisterHandler('iq',self._CommandHandler,typ='set',ns=NS_COMMANDS)
- owner.RegisterHandler('iq',self._CommandHandler,typ='get',ns=NS_COMMANDS)
- self._browser.setDiscoHandler(self._DiscoHandler,node=NS_COMMANDS,jid='')
- owner.debug_flags.append(DBG_COMMANDS)
-
- def plugout(self):
- """Removes handlers from the session"""
- # unPlug from the session and the disco manager
- self._owner.UnregisterHandler('iq',self._CommandHandler,ns=NS_COMMANDS)
- for jid in self._handlers:
- self._browser.delDiscoHandler(self._DiscoHandler,node=NS_COMMANDS,jid=jid)
-
- def _CommandHandler(self,conn,request):
- """The internal method to process the routing of command execution requests"""
- # This is the command handler itself.
- # We must:
- # Pass on command execution to command handler
- # (Do we need to keep session details here, or can that be done in the command?)
- jid = str(request.getTo())
- try:
- node = request.getTagAttr('command','node')
- except Exception:
- conn.send(Error(request,ERR_BAD_REQUEST))
- raise NodeProcessed
- if jid in self._handlers:
- if node in self._handlers[jid]:
- self._handlers[jid][node]['execute'](conn,request)
- else:
- conn.send(Error(request,ERR_ITEM_NOT_FOUND))
- raise NodeProcessed
- elif node in self._handlers['']:
- self._handlers[''][node]['execute'](conn,request)
- else:
- conn.send(Error(request,ERR_ITEM_NOT_FOUND))
- raise NodeProcessed
-
- def _DiscoHandler(self,conn,request,typ):
- """The internal method to process service discovery requests"""
- # This is the disco manager handler.
- if typ == 'items':
- # We must:
- # Generate a list of commands and return the list
- # * This handler does not handle individual commands disco requests.
- # Pseudo:
- # Enumerate the 'item' disco of each command for the specified jid
- # Build responce and send
- # To make this code easy to write we add an 'list' disco type, it returns a tuple or 'none' if not advertised
- list_ = []
- items = []
- jid = str(request.getTo())
- # Get specific jid based results
- if jid in self._handlers:
- for each in self._handlers[jid].keys():
- items.append((jid,each))
- else:
- # Get generic results
- for each in self._handlers[''].keys():
- items.append(('',each))
- if items != []:
- for each in items:
- i = self._handlers[each[0]][each[1]]['disco'](conn,request,'list')
- if i is not None:
- list_.append(Node(tag='item',attrs={'jid':i[0],'node':i[1],'name':i[2]}))
- iq = request.buildReply('result')
- if request.getQuerynode(): iq.setQuerynode(request.getQuerynode())
- iq.setQueryPayload(list_)
- conn.send(iq)
- else:
- conn.send(Error(request,ERR_ITEM_NOT_FOUND))
- raise NodeProcessed
- elif typ == 'info':
- return {'ids':[{'category':'automation','type':'command-list'}],'features':[]}
-
- def addCommand(self,name,cmddisco,cmdexecute,jid=''):
- """The method to call if adding a new command to the session, the requred parameters of cmddisco and cmdexecute are the methods to enable that command to be executed"""
- # This command takes a command object and the name of the command for registration
- # We must:
- # Add item into disco
- # Add item into command list
- if jid not in self._handlers:
- self._handlers[jid]={}
- self._browser.setDiscoHandler(self._DiscoHandler,node=NS_COMMANDS,jid=jid)
- if name in self._handlers[jid]:
- raise NameError,'Command Exists'
- else:
- self._handlers[jid][name]={'disco':cmddisco,'execute':cmdexecute}
- # Need to add disco stuff here
- self._browser.setDiscoHandler(cmddisco,node=name,jid=jid)
-
- def delCommand(self,name,jid=''):
- """Removed command from the session"""
- # This command takes a command object and the name used for registration
- # We must:
- # Remove item from disco
- # Remove item from command list
- if jid not in self._handlers:
- raise NameError,'Jid not found'
- if name not in self._handlers[jid]:
- raise NameError, 'Command not found'
- else:
- #Do disco removal here
- command = self.getCommand(name,jid)['disco']
- del self._handlers[jid][name]
- self._browser.delDiscoHandler(command,node=name,jid=jid)
-
- def getCommand(self,name,jid=''):
- """Returns the command tuple"""
- # This gets the command object with name
- # We must:
- # Return item that matches this name
- if jid not in self._handlers:
- raise NameError,'Jid not found'
- elif name not in self._handlers[jid]:
- raise NameError,'Command not found'
- else:
- return self._handlers[jid][name]
-
-class Command_Handler_Prototype(PlugIn):
- """This is a prototype command handler, as each command uses a disco method
- and execute method you can implement it any way you like, however this is
- my first attempt at making a generic handler that you can hang process
- stages on too. There is an example command below.
-
- The parameters are as follows:
- name : the name of the command within the jabber environment
- description : the natural language description
- discofeatures : the features supported by the command
- initial : the initial command in the from of {'execute':commandname}
-
- All stages set the 'actions' dictionary for each session to represent the possible options available.
- """
- name = 'examplecommand'
- count = 0
- description = 'an example command'
- discofeatures = [NS_COMMANDS,NS_DATA]
- # This is the command template
- def __init__(self,jid=''):
- """Set up the class"""
- PlugIn.__init__(self)
- self.DBG_LINE='command'
- self.sessioncount = 0
- self.sessions = {}
- # Disco information for command list pre-formatted as a tuple
- self.discoinfo = {'ids':[{'category':'automation','type':'command-node','name':self.description}],'features': self.discofeatures}
- self._jid = jid
-
- def plugin(self,owner):
- """Plug command into the commands class"""
- # The owner in this instance is the Command Processor
- self._commands = owner
- self._owner = owner._owner
- self._commands.addCommand(self.name,self._DiscoHandler,self.Execute,jid=self._jid)
-
- def plugout(self):
- """Remove command from the commands class"""
- self._commands.delCommand(name,self._jid)
-
- def getSessionID(self):
- """Returns an id for the command session"""
- self.count = self.count+1
- return 'cmd-%s-%d'%(self.name,self.count)
-
- def Execute(self,conn,request):
- """The method that handles all the commands, and routes them to the correct method for that stage."""
- # New request or old?
- try:
- session = request.getTagAttr('command','sessionid')
- except Exception:
- session = None
- try:
- action = request.getTagAttr('command','action')
- except Exception:
- action = None
- if action is None: action = 'execute'
- # Check session is in session list
- if session in self.sessions:
- if self.sessions[session]['jid']==request.getFrom():
- # Check action is vaild
- if action in self.sessions[session]['actions']:
- # Execute next action
- self.sessions[session]['actions'][action](conn,request)
- else:
- # Stage not presented as an option
- self._owner.send(Error(request,ERR_BAD_REQUEST))
- raise NodeProcessed
- else:
- # Jid and session don't match. Go away imposter
- self._owner.send(Error(request,ERR_BAD_REQUEST))
- raise NodeProcessed
- elif session is not None:
- # Not on this sessionid you won't.
- self._owner.send(Error(request,ERR_BAD_REQUEST))
- raise NodeProcessed
- else:
- # New session
- self.initial[action](conn,request)
-
- def _DiscoHandler(self,conn,request,type_):
- """The handler for discovery events"""
- if type_ == 'list':
- return (request.getTo(),self.name,self.description)
- elif type_ == 'items':
- return []
- elif type_ == 'info':
- return self.discoinfo
-
-class TestCommand(Command_Handler_Prototype):
- """ Example class. You should read source if you wish to understate how it works.
- Generally, it presents a "master" that giudes user through to calculate something.
- """
- name = 'testcommand'
- description = 'a noddy example command'
- def __init__(self,jid=''):
- """ Init internal constants. """
- Command_Handler_Prototype.__init__(self,jid)
- self.pi = 3.14
- self.initial = {'execute':self.cmdFirstStage}
-
- def cmdFirstStage(self,conn,request):
- """ Determine """
- # This is the only place this should be repeated as all other stages should have SessionIDs
- try:
- session = request.getTagAttr('command','sessionid')
- except Exception:
- session = None
- if session is None:
- session = self.getSessionID()
- self.sessions[session]={'jid':request.getFrom(),'actions':{'cancel':self.cmdCancel,'next':self.cmdSecondStage},'data':{'type':None}}
- # As this is the first stage we only send a form
- reply = request.buildReply('result')
- form = DataForm(title='Select type of operation',data=['Use the combobox to select the type of calculation you would like to do, then click Next',DataField(name='calctype',label='Calculation Type',value=self.sessions[session]['data']['type'],options=[['circlediameter','Calculate the Diameter of a circle'],['circlearea','Calculate the area of a circle']],typ='list-single',required=1)])
- replypayload = [Node('actions',attrs={'execute':'next'},payload=[Node('next')]),form]
- reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':session,'status':'executing'},payload=replypayload)
- self._owner.send(reply)
- raise NodeProcessed
-
- def cmdSecondStage(self,conn,request):
- form = DataForm(node = result.getTag(name='command').getTag(name='x',namespace=NS_DATA))
- sessions[request.getTagAttr('command','sessionid')]['data']['type']=form.getField('calctype')
- sessions[request.getTagAttr('command','sessionid')]['actions']={'cancel':self.cmdCancel,None:self.cmdThirdStage,'previous':cmdFirstStage}
- # The form generation is split out to another method as it may be called by cmdThirdStage
- self.cmdSecondStageReply(conn,request)
-
- def cmdSecondStageReply(self,conn,request):
- reply = request.buildReply('result')
- form = DataForm(title = 'Enter the radius', data=['Enter the radius of the circle (numbers only)',DataField(label='Radius',name='radius',typ='text-single')])
- replypayload = [Node('actions',attrs={'execute':'complete'},payload=[Node('complete'),Node('prev')]),form]
- reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'executing'},payload=replypayload)
- self._owner.send(reply)
- raise NodeProcessed
-
- def cmdThirdStage(self,conn,request):
- form = DataForm(node = result.getTag(name='command').getTag(name='x',namespace=NS_DATA))
- try:
- num = float(form.getField('radius'))
- except Exception:
- self.cmdSecondStageReply(conn,request)
- if sessions[request.getTagAttr('command','sessionid')]['data']['type'] == 'circlearea':
- result = num*(math.pi**2)
- else:
- result = num*2*math.pi
- reply = result.buildReply(request)
- form = DataForm(typ='result',data=[DataField(label='result',name='result',value=result)])
- reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'completed'},payload=form)
- self._owner.send(reply)
- raise NodeProcessed
-
- def cmdCancel(self,conn,request):
- reply = request.buildReply('result')
- reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'cancelled'})
- self._owner.send(reply)
- del self.sessions[request.getTagAttr('command','sessionid')]
-
-
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/debug.py b/src/common/xmpp/debug.py
index 4107ec895..94758e35c 100644
--- a/src/common/xmpp/debug.py
+++ b/src/common/xmpp/debug.py
@@ -12,7 +12,7 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU Lesser General Public License for more details.
-"""
+'''
Generic debug class
Other modules can always define extra debug flags for local usage, as long as
@@ -31,7 +31,7 @@ by the individual classes.
For samples of usage, see samples subdir in distro source, and selftest
in this code
-"""
+'''
_version_ = '1.4.0'
@@ -147,7 +147,7 @@ class Debug:
# If you dont want to validate flags on each call to
# show(), set this to 0
#
- validate_flags = 1,
+ validate_flags = 0,
#
# If you dont want the welcome message, set to 0
# default is to show welcome if any flags are active
@@ -202,7 +202,7 @@ class Debug:
def show( self, msg, flag = None, prefix = None, sufix = None,
lf = 0 ):
- """
+ '''
flag can be of folowing types:
None - this msg will always be shown if any debugging is on
flag - will be shown if flag is active
@@ -213,7 +213,7 @@ class Debug:
lf = -1 means strip linefeed if pressent
lf = 1 means add linefeed if not pressent
- """
+ '''
if self.validate_flags:
self._validate_flag( flag )
@@ -313,10 +313,10 @@ class Debug:
def _as_one_list( self, items ):
- """ init param might contain nested lists, typically from group flags.
+ ''' init param might contain nested lists, typically from group flags.
This code organises lst and remves dupes
- """
+ '''
if not isinstance(items, (list, tuple)):
return [ items ]
r = []
@@ -333,7 +333,7 @@ class Debug:
def _append_unique_str( self, lst, item ):
- """filter out any dupes."""
+ '''filter out any dupes.'''
if not isinstance(item, str):
msg2 = '%s' % item
raise Exception('Invalid item type "%s", should be string' % \
@@ -352,10 +352,10 @@ class Debug:
raise Exception('Invalid debugflag "%s" given' % f)
def _remove_dupe_flags( self ):
- """
+ '''
if multiple instances of Debug is used in same app,
some flags might be created multiple time, filter out dupes
- """
+ '''
unique_flags = []
for f in self.debug_flags:
if f not in unique_flags:
@@ -364,6 +364,7 @@ class Debug:
colors={}
def Show(self, flag, msg, prefix=''):
+ msg=str(msg)
msg=msg.replace('\r','\\r').replace('\n','\\n').replace('><','>\n <')
if not colors_enabled: pass
elif prefix in self.colors: msg=self.colors[prefix]+msg+color_none
diff --git a/src/common/xmpp/dispatcher.py b/src/common/xmpp/dispatcher.py
deleted file mode 100644
index 1fd7a6495..000000000
--- a/src/common/xmpp/dispatcher.py
+++ /dev/null
@@ -1,386 +0,0 @@
-## transports.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-# $Id: dispatcher.py,v 1.40 2006/01/18 19:26:43 normanr Exp $
-
-"""
-Main xmpppy mechanism. Provides library with methods to assign different handlers
-to different XMPP stanzas.
-Contains one tunable attribute: DefaultTimeout (25 seconds by default). It defines time that
-Dispatcher.SendAndWaitForResponce method will wait for reply stanza before giving up.
-"""
-
-import simplexml,time,sys
-from protocol import *
-from client import PlugIn
-
-DefaultTimeout=25
-ID=0
-
-DBG_DISPATCHER = 'dispatcher'
-
-class Dispatcher(PlugIn):
- """ Ancestor of PlugIn class. Handles XMPP stream, i.e. aware of stream headers.
- Can be plugged out/in to restart these headers (used for SASL f.e.). """
- def __init__(self):
- PlugIn.__init__(self)
- self.DBG_LINE = DBG_DISPATCHER
- self.handlers={}
- self._expected={}
- self._defaultHandler=None
- self._pendingExceptions=[]
- self._eventHandler=None
- self._cycleHandlers=[]
- self._exported_methods=[self.Process,self.RegisterHandler,self.RegisterDefaultHandler,\
- self.RegisterEventHandler,self.UnregisterCycleHandler,self.RegisterCycleHandler,\
- self.RegisterHandlerOnce,self.UnregisterHandler,self.RegisterProtocol,\
- self.WaitForResponse,self.SendAndWaitForResponse,self.send,self.disconnect,\
- self.SendAndCallForResponse, self.getAnID, ]
-
- def getAnID(self):
- global ID
- ID+=1
- return repr(ID)
-
- def dumpHandlers(self):
- """ Return set of user-registered callbacks in it's internal format.
- Used within the library to carry user handlers set over Dispatcher replugins. """
- return self.handlers
- def restoreHandlers(self,handlers):
- """ Restores user-registered callbacks structure from dump previously obtained via dumpHandlers.
- Used within the library to carry user handlers set over Dispatcher replugins. """
- self.handlers=handlers
-
- def _init(self):
- """ Registers default namespaces/protocols/handlers. Used internally. """
- self.RegisterNamespace('unknown')
- self.RegisterNamespace(NS_STREAMS)
- self.RegisterNamespace(self._owner.defaultNamespace)
- self.RegisterProtocol('iq',Iq)
- self.RegisterProtocol('presence',Presence)
- self.RegisterProtocol('message',Message)
- self.RegisterDefaultHandler(self.returnStanzaHandler)
- # Register Gajim's event handler as soon as dispatcher begins
- self.RegisterEventHandler(self._owner._caller._event_dispatcher)
-# self.RegisterHandler('error',self.streamErrorHandler,xmlns=NS_STREAMS)
-
- def plugin(self, owner):
- """ Plug the Dispatcher instance into Client class instance and send initial stream header. Used internally."""
- self._init()
- for method in self._old_owners_methods:
- if method.__name__=='send': self._owner_send=method; break
- self._owner.lastErrNode=None
- self._owner.lastErr=None
- self._owner.lastErrCode=None
- self.StreamInit()
- owner.debug_flags.append(DBG_DISPATCHER)
-
- def plugout(self):
- """ Prepares instance to be destructed. """
- self.Stream.dispatch=None
- self.Stream.DEBUG=None
- self.Stream.features=None
- self.Stream.destroy()
-
- def StreamInit(self):
- """ Send an initial stream header. """
- self.Stream=simplexml.NodeBuilder()
- self.Stream._dispatch_depth=2
- self.Stream.dispatch=self.dispatch
- self.Stream.stream_header_received=self._check_stream_start
- self._owner.debug_flags.append(simplexml.DBG_NODEBUILDER)
- self.Stream.DEBUG=self._owner.DEBUG
- self.Stream.features=None
- self._metastream=Node('stream:stream')
- self._metastream.setNamespace(self._owner.Namespace)
- self._metastream.setAttr('version','1.0')
- self._metastream.setAttr('xmlns:stream',NS_STREAMS)
- self._metastream.setAttr('to',self._owner.Server)
- self._owner.send("%s>"%str(self._metastream)[:-2])
-
- def _check_stream_start(self,ns,tag,attrs):
- if ns!=NS_STREAMS or tag!='stream':
- raise ValueError('Incorrect stream start: (%s,%s). Terminating.'%(tag,ns))
-
- def Process(self, timeout=0):
- """ Check incoming stream for data waiting. If "timeout" is positive - block for as max. this time.
- Returns:
- 1) length of processed data if some data were processed;
- 2) '0' string if no data were processed but link is alive;
- 3) 0 (zero) if underlying connection is closed.
- Take note that in case of disconnection detect during Process() call
- disconnect handlers are called automatically.
- """
- for handler in self._cycleHandlers: handler(self)
- if len(self._pendingExceptions) > 0:
- _pendingException = self._pendingExceptions.pop()
- raise _pendingException[0], _pendingException[1], _pendingException[2]
- if self._owner.Connection.pending_data(timeout):
- try: data=self._owner.Connection.receive()
- except IOError: return
- self.Stream.Parse(data)
- if len(self._pendingExceptions) > 0:
- _pendingException = self._pendingExceptions.pop()
- raise _pendingException[0], _pendingException[1], _pendingException[2]
- return len(data)
- return '0' # It means that nothing is received but link is alive.
-
- def RegisterNamespace(self,xmlns,order='info'):
- """ Creates internal structures for newly registered namespace.
- You can register handlers for this namespace afterwards. By default one namespace
- already registered (jabber:client or jabber:component:accept depending on context. """
- self.DEBUG('Registering namespace "%s"'%xmlns,order)
- self.handlers[xmlns]={}
- self.RegisterProtocol('unknown',Protocol,xmlns=xmlns)
- self.RegisterProtocol('default',Protocol,xmlns=xmlns)
-
- def RegisterProtocol(self,tag_name,Proto,xmlns=None,order='info'):
- """ Used to declare some top-level stanza name to dispatcher.
- Needed to start registering handlers for such stanzas.
- Iq, message and presence protocols are registered by default. """
- if not xmlns: xmlns=self._owner.defaultNamespace
- self.DEBUG('Registering protocol "%s" as %s(%s)'%(tag_name,Proto,xmlns), order)
- self.handlers[xmlns][tag_name]={type:Proto, 'default':[]}
-
- def RegisterNamespaceHandler(self,xmlns,handler,typ='',ns='', makefirst=0, system=0):
- """ Register handler for processing all stanzas for specified namespace. """
- self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, system)
-
- def RegisterHandler(self,name,handler,typ='',ns='',xmlns=None, makefirst=0, system=0):
- """Register user callback as stanzas handler of declared type. Callback must take
- (if chained, see later) arguments: dispatcher instance (for replying), incomed
- return of previous handlers.
- The callback must raise xmpp.NodeProcessed just before return if it want preven
- callbacks to be called with the same stanza as argument _and_, more importantly
- library from returning stanza to sender with error set (to be enabled in 0.2 ve
- Arguments:
- "name" - name of stanza. F.e. "iq".
- "handler" - user callback.
- "typ" - value of stanza's "type" attribute. If not specified any value match
- "ns" - namespace of child that stanza must contain.
- "chained" - chain together output of several handlers.
- "makefirst" - insert handler in the beginning of handlers list instead of
- adding it to the end. Note that more common handlers (i.e. w/o "typ" and "
- will be called first nevertheless.
- "system" - call handler even if NodeProcessed Exception were raised already.
- """
- if not xmlns: xmlns=self._owner.defaultNamespace
- self.DEBUG('Registering handler %s for "%s" type->%s ns->%s(%s)'%(handler,name,typ,ns,xmlns), 'info')
- if not typ and not ns: typ='default'
- if xmlns not in self.handlers: self.RegisterNamespace(xmlns,'warn')
- if name not in self.handlers[xmlns]: self.RegisterProtocol(name,Protocol,xmlns,'warn')
- if typ+ns not in self.handlers[xmlns][name]: self.handlers[xmlns][name][typ+ns]=[]
- if makefirst: self.handlers[xmlns][name][typ+ns].insert(0,{'func':handler,'system':system})
- else: self.handlers[xmlns][name][typ+ns].append({'func':handler,'system':system})
-
- def RegisterHandlerOnce(self,name,handler,typ='',ns='',xmlns=None,makefirst=0, system=0):
- """ Unregister handler after first call (not implemented yet). """
- if not xmlns: xmlns=self._owner.defaultNamespace
- self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system)
-
- def UnregisterHandler(self,name,handler,typ='',ns='',xmlns=None):
- """ Unregister handler. "typ" and "ns" must be specified exactly the same as with registering."""
- if not xmlns: xmlns=self._owner.defaultNamespace
- if not typ and not ns: typ='default'
- if name not in self.handlers[xmlns]: return
- if typ+ns not in self.handlers[xmlns][name]: return
- for pack in self.handlers[xmlns][name][typ+ns]:
- if handler==pack['func']: break
- else: pack=None
- try: self.handlers[xmlns][name][typ+ns].remove(pack)
- except ValueError: pass
-
- def RegisterDefaultHandler(self,handler):
- """ Specify the handler that will be used if no NodeProcessed exception were raised.
- This is returnStanzaHandler by default. """
- self._defaultHandler=handler
-
- def RegisterEventHandler(self,handler):
- """ Register handler that will process events. F.e. "FILERECEIVED" event. """
- self._eventHandler=handler
-
- def returnStanzaHandler(self,conn,stanza):
- """ Return stanza back to the sender with error set. """
- if stanza.getType() in ('get','set'):
- conn.send(Error(stanza,ERR_FEATURE_NOT_IMPLEMENTED))
-
- def streamErrorHandler(self,conn,error):
- name,text='error',error.getData()
- for tag in error.getChildren():
- if tag.getNamespace()==NS_XMPP_STREAMS:
- if tag.getName()=='text': text=tag.getData()
- else: name=tag.getName()
- if name in stream_exceptions.keys(): exc=stream_exceptions[name]
- else: exc=StreamError
- raise exc((name,text))
-
- def RegisterCycleHandler(self,handler):
- """ Register handler that will be called on every Dispatcher.Process() call. """
- if handler not in self._cycleHandlers: self._cycleHandlers.append(handler)
-
- def UnregisterCycleHandler(self,handler):
- """ Unregister handler that will is called on every Dispatcher.Process() call."""
- if handler in self._cycleHandlers: self._cycleHandlers.remove(handler)
-
- def Event(self,realm,event,data):
- """ Raise some event. Takes three arguments:
- 1) "realm" - scope of event. Usually a namespace.
- 2) "event" - the event itself. F.e. "SUCESSFULL SEND".
- 3) data that comes along with event. Depends on event."""
- if self._eventHandler: self._eventHandler(realm,event,data)
-
- def dispatch(self,stanza,session=None,direct=0):
- """ Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it.
- Called internally. """
- if not session: session=self
- session.Stream._mini_dom=None
- name=stanza.getName()
-
- if not direct and self._owner._component:
- if name == 'route':
- if stanza.getAttr('error') is None:
- if len(stanza.getChildren()) == 1:
- stanza = stanza.getChildren()[0]
- name=stanza.getName()
- else:
- for each in stanza.getChildren():
- self.dispatch(each,session,direct=1)
- return
- elif name == 'presence':
- return
- elif name in ('features','bind'):
- pass
- else:
- raise UnsupportedStanzaType(name)
-
- if name=='features': session.Stream.features=stanza
-
- xmlns=stanza.getNamespace()
- if xmlns not in self.handlers:
- self.DEBUG("Unknown namespace: " + xmlns,'warn')
- xmlns='unknown'
- if name not in self.handlers[xmlns]:
- self.DEBUG("Unknown stanza: " + name,'warn')
- name='unknown'
- else:
- self.DEBUG("Got %s/%s stanza"%(xmlns,name), 'ok')
-
- if stanza.__class__.__name__=='Node': stanza=self.handlers[xmlns][name][type](node=stanza)
-
- typ=stanza.getType()
- if not typ: typ=''
- stanza.props=stanza.getProperties()
- ID=stanza.getID()
-
- session.DEBUG("Dispatching %s stanza with type->%s props->%s id->%s"%(name,typ,stanza.props,ID),'ok')
-
- list_=['default'] # we will use all handlers:
- if typ in self.handlers[xmlns][name]: list_.append(typ) # from very common...
- for prop in stanza.props:
- if prop in self.handlers[xmlns][name]: list_.append(prop)
- if typ and typ+prop in self.handlers[xmlns][name]: list_.append(typ+prop) # ...to very particular
-
- chain=self.handlers[xmlns]['default']['default']
- for key in list_:
- if key: chain = chain + self.handlers[xmlns][name][key]
-
- if ID in session._expected:
- user=0
- if isinstance(session._expected[ID], tuple):
- cb,args=session._expected[ID]
- session.DEBUG("Expected stanza arrived. Callback %s(%s) found!"%(cb,args),'ok')
- try: cb(session,stanza,**args)
- except Exception, typ:
- if typ.__class__.__name__!='NodeProcessed': raise
- else:
- session.DEBUG("Expected stanza arrived!",'ok')
- session._expected[ID]=stanza
- else: user=1
- for handler in chain:
- if user or handler['system']:
- try:
- handler['func'](session,stanza)
- except Exception, typ:
- if typ.__class__.__name__!='NodeProcessed':
- self._pendingExceptions.insert(0, sys.exc_info())
- return
- user=0
- if user and self._defaultHandler: self._defaultHandler(session,stanza)
-
- def WaitForResponse(self, ID, timeout=None):
- """ Block and wait until stanza with specific "id" attribute will come.
- If no such stanza is arrived within timeout, return None.
- If operation failed for some reason then owner's attributes
- lastErrNode, lastErr and lastErrCode are set accordingly. """
- if timeout is None: timeout=DefaultTimeout
- self._expected[ID]=None
- abort_time=time.time() + timeout
- self.DEBUG("Waiting for ID:%s with timeout %s..." % (ID,timeout),'wait')
- while not self._expected[ID]:
- if not self.Process(0.04):
- self._owner.lastErr="Disconnect"
- return None
- if time.time() > abort_time:
- self._owner.lastErr="Timeout"
- return None
- response=self._expected[ID]
- del self._expected[ID]
- if response.getErrorCode():
- self._owner.lastErrNode=response
- self._owner.lastErr=response.getError()
- self._owner.lastErrCode=response.getErrorCode()
- return response
-
- def SendAndWaitForResponse(self, stanza, timeout=None):
- """ Put stanza on the wire and wait for recipient's response to it. """
- if timeout is None: timeout=DefaultTimeout
- return self.WaitForResponse(self.send(stanza),timeout)
-
- def SendAndCallForResponse(self, stanza, func, args={}):
- """ Put stanza on the wire and call back when recipient replies.
- Additional callback arguments can be specified in args. """
- self._expected[self.send(stanza)]=(func,args)
-
- def send(self,stanza):
- """ Serialise stanza and put it on the wire. Assign an unique ID to it before send.
- Returns assigned ID."""
- if isinstance(stanza, basestring): return self._owner_send(stanza)
- if not isinstance(stanza,Protocol): _ID=None
- elif not stanza.getID():
- global ID
- ID+=1
- _ID=repr(ID)
- stanza.setID(_ID)
- else: _ID=stanza.getID()
- if self._owner._registered_name and not stanza.getAttr('from'): stanza.setAttr('from',self._owner._registered_name)
- if self._owner._component and stanza.getName()!='bind':
- to=self._owner.Server
- if stanza.getTo() and stanza.getTo().getDomain():
- to=stanza.getTo().getDomain()
- frm=stanza.getFrom()
- if frm.getDomain():
- frm=frm.getDomain()
- route=Protocol('route',to=to,frm=frm,payload=[stanza])
- stanza=route
- stanza.setNamespace(self._owner.Namespace)
- stanza.setParent(self._metastream)
- self._owner_send(stanza)
- return _ID
-
- def disconnect(self):
- """ Send a stream terminator and and handle all incoming stanzas before stream closure. """
- self._owner_send('')
- while self.Process(1): pass
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py
index 7e9e5df48..57dc3f777 100644
--- a/src/common/xmpp/dispatcher_nb.py
+++ b/src/common/xmpp/dispatcher_nb.py
@@ -14,7 +14,6 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
-# $Id: dispatcher.py,v 1.40 2006/01/18 19:26:43 normanr Exp $
'''
Main xmpppy mechanism. Provides library with methods to assign different handlers
@@ -28,16 +27,39 @@ from xml.parsers.expat import ExpatError
from protocol import *
from client import PlugIn
+import logging
+log = logging.getLogger('gajim.c.x.dispatcher_nb')
+
# default timeout to wait for response for our id
DEFAULT_TIMEOUT_SECONDS = 25
ID = 0
-class Dispatcher(PlugIn):
+XML_DECLARATION = ''
+
+# FIXME: ugly
+class Dispatcher():
+# Why is this here - I needed to redefine Dispatcher for BOSH and easiest way
+# was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble
+# is that reference used to access dispatcher instance is in Client attribute
+# named by __class__.__name__ of the dispatcher instance .. long story short:
+# I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp/
+
+# If having two kinds of dispatcher will go well, I will rewrite the dispatcher
+# references in other scripts
+ def PlugIn(self, client_obj, after_SASL=False, old_features=None):
+ if client_obj.protocol_type == 'XMPP':
+ XMPPDispatcher().PlugIn(client_obj)
+ elif client_obj.protocol_type == 'BOSH':
+ BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features)
+
+
+
+class XMPPDispatcher(PlugIn):
''' Ancestor of PlugIn class. Handles XMPP stream, i.e. aware of stream headers.
- Can be plugged out/in to restart these headers (used for SASL f.e.). '''
+ Can be plugged out/in to restart these headers (used for SASL f.e.). '''
+
def __init__(self):
PlugIn.__init__(self)
- self.DBG_LINE='dispatcher'
self.handlers={}
self._expected={}
self._defaultHandler=None
@@ -47,8 +69,8 @@ class Dispatcher(PlugIn):
self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, \
self.RegisterEventHandler, self.UnregisterCycleHandler, self.RegisterCycleHandler, \
self.RegisterHandlerOnce, self.UnregisterHandler, self.RegisterProtocol, \
- self.SendAndWaitForResponse, self.send,self.disconnect, \
- self.SendAndCallForResponse, self.getAnID, self.Event]
+ self.SendAndWaitForResponse, \
+ self.SendAndCallForResponse, self.getAnID, self.Event, self.send]
def getAnID(self):
global ID
@@ -83,15 +105,11 @@ class Dispatcher(PlugIn):
self._owner.lastErrNode = None
self._owner.lastErr = None
self._owner.lastErrCode = None
- if hasattr(self._owner, 'StreamInit'):
- self._owner.StreamInit()
- else:
- self.StreamInit()
+ self.StreamInit()
def plugout(self):
''' Prepares instance to be destructed. '''
self.Stream.dispatch = None
- self.Stream.DEBUG = None
self.Stream.features = None
self.Stream.destroy()
self._owner = None
@@ -100,11 +118,9 @@ class Dispatcher(PlugIn):
def StreamInit(self):
''' Send an initial stream header. '''
self.Stream = simplexml.NodeBuilder()
- self.Stream._dispatch_depth = 2
self.Stream.dispatch = self.dispatch
+ self.Stream._dispatch_depth = 2
self.Stream.stream_header_received = self._check_stream_start
- self._owner.debug_flags.append(simplexml.DBG_NODEBUILDER)
- self.Stream.DEBUG = self._owner.DEBUG
self.Stream.features = None
self._metastream = Node('stream:stream')
self._metastream.setNamespace(self._owner.Namespace)
@@ -114,7 +130,7 @@ class Dispatcher(PlugIn):
if locale.getdefaultlocale()[0]:
self._metastream.setAttr('xml:lang',
locale.getdefaultlocale()[0].split('_')[0])
- self._owner.send("%s>" % str(self._metastream)[:-2])
+ self._owner.send("%s%s>" % (XML_DECLARATION,str(self._metastream)[:-2]))
def _check_stream_start(self, ns, tag, attrs):
if ns!=NS_STREAMS or tag!='stream':
@@ -135,11 +151,11 @@ class Dispatcher(PlugIn):
self.Stream.Parse(data)
# end stream:stream tag received
if self.Stream and self.Stream.has_received_endtag():
- self._owner.Connection.disconnect()
+ self._owner.disconnect()
return 0
except ExpatError:
- self.DEBUG('Invalid XML received from server. Forcing disconnect.', 'error')
- self._owner.Connection.pollend()
+ log.error('Invalid XML received from server. Forcing disconnect.')
+ self._owner.Connection.disconnect()
return 0
except ValueError, e:
self.DEBUG(str(e), 'error')
@@ -155,7 +171,7 @@ class Dispatcher(PlugIn):
''' Creates internal structures for newly registered namespace.
You can register handlers for this namespace afterwards. By default one namespace
already registered (jabber:client or jabber:component:accept depending on context. '''
- self.DEBUG('Registering namespace "%s"' % xmlns, order)
+ log.debug('Registering namespace "%s"' % xmlns)
self.handlers[xmlns]={}
self.RegisterProtocol('unknown', Protocol, xmlns=xmlns)
self.RegisterProtocol('default', Protocol, xmlns=xmlns)
@@ -165,8 +181,7 @@ class Dispatcher(PlugIn):
Needed to start registering handlers for such stanzas.
Iq, message and presence protocols are registered by default. '''
if not xmlns: xmlns=self._owner.defaultNamespace
- self.DEBUG('Registering protocol "%s" as %s(%s)' %
- (tag_name, Proto, xmlns), order)
+ log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns))
self.handlers[xmlns][tag_name]={type:Proto, 'default':[]}
def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='', makefirst=0, system=0):
@@ -193,8 +208,8 @@ class Dispatcher(PlugIn):
'''
if not xmlns:
xmlns=self._owner.defaultNamespace
- self.DEBUG('Registering handler %s for "%s" type->%s ns->%s(%s)' %
- (handler, name, typ, ns, xmlns), 'info')
+ log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' %
+ (handler, name, typ, ns, xmlns))
if not typ and not ns:
typ='default'
if xmlns not in self.handlers:
@@ -245,7 +260,7 @@ class Dispatcher(PlugIn):
def returnStanzaHandler(self,conn,stanza):
''' Return stanza back to the sender with error set. '''
if stanza.getType() in ('get','set'):
- conn.send(Error(stanza,ERR_FEATURE_NOT_IMPLEMENTED))
+ conn._owner.send(Error(stanza,ERR_FEATURE_NOT_IMPLEMENTED))
def streamErrorHandler(self,conn,error):
name,text='error',error.getData()
@@ -281,40 +296,31 @@ class Dispatcher(PlugIn):
def dispatch(self, stanza, session=None, direct=0):
''' Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it.
Called internally. '''
+ #log.info('dispatch called: stanza = %s, session = %s, direct= %s' % (stanza, session, direct))
if not session:
session = self
session.Stream._mini_dom = None
name = stanza.getName()
- if not direct and self._owner._component:
- if name == 'route':
- if stanza.getAttr('error') is None:
- if len(stanza.getChildren()) == 1:
- stanza = stanza.getChildren()[0]
- name=stanza.getName()
- else:
- for each in stanza.getChildren():
- self.dispatch(each,session,direct=1)
- return
- elif name == 'presence':
- return
- elif name in ('features','bind'):
- pass
- else:
- raise UnsupportedStanzaType(name)
-
if name=='features':
+ self._owner.got_features = True
session.Stream.features=stanza
xmlns=stanza.getNamespace()
+
+ #log.info('in dispatch, getting ns for %s, and the ns is %s' % (stanza, xmlns))
if xmlns not in self.handlers:
- self.DEBUG("Unknown namespace: " + xmlns, 'warn')
+ log.warn("Unknown namespace: " + xmlns)
xmlns='unknown'
+ # features stanza has been handled before
if name not in self.handlers[xmlns]:
- self.DEBUG("Unknown stanza: " + name, 'warn')
+ if name != 'features':
+ log.warn("Unknown stanza: " + name)
+ else:
+ log.debug("Got %s/%s stanza" % (xmlns, name))
name='unknown'
else:
- self.DEBUG("Got %s/%s stanza" % (xmlns, name), 'ok')
+ log.debug("Got %s/%s stanza" % (xmlns, name))
if stanza.__class__.__name__=='Node':
stanza=self.handlers[xmlns][name][type](node=stanza)
@@ -324,7 +330,6 @@ class Dispatcher(PlugIn):
stanza.props=stanza.getProperties()
ID=stanza.getID()
- session.DEBUG("Dispatching %s stanza with type->%s props->%s id->%s"%(name,typ,stanza.props,ID),'ok')
list_=['default'] # we will use all handlers:
if typ in self.handlers[xmlns][name]: list_.append(typ) # from very common...
for prop in stanza.props:
@@ -339,13 +344,13 @@ class Dispatcher(PlugIn):
user=0
if isinstance(session._expected[ID], tuple):
cb,args = session._expected[ID]
- session.DEBUG("Expected stanza arrived. Callback %s(%s) found!" % (cb, args), 'ok')
+ log.debug("Expected stanza arrived. Callback %s(%s) found!" % (cb, args))
try:
cb(session,stanza,**args)
except Exception, typ:
if typ.__class__.__name__ !='NodeProcessed': raise
else:
- session.DEBUG("Expected stanza arrived!",'ok')
+ log.debug("Expected stanza arrived!")
session._expected[ID]=stanza
else:
user=1
@@ -401,41 +406,85 @@ class Dispatcher(PlugIn):
Additional callback arguments can be specified in args. '''
self.SendAndWaitForResponse(stanza, 0, func, args)
- def send(self, stanza, is_message = False, now = False):
- ''' Serialise stanza and put it on the wire. Assign an unique ID to it before send.
- Returns assigned ID.'''
- if isinstance(stanza, basestring):
- return self._owner.Connection.send(stanza, now = now)
- if not isinstance(stanza, Protocol):
- _ID=None
- elif not stanza.getID():
- global ID
- ID+=1
- _ID=repr(ID)
- stanza.setID(_ID)
- else:
- _ID=stanza.getID()
- if self._owner._registered_name and not stanza.getAttr('from'):
- stanza.setAttr('from', self._owner._registered_name)
- if self._owner._component and stanza.getName() != 'bind':
- to=self._owner.Server
- if stanza.getTo() and stanza.getTo().getDomain():
- to=stanza.getTo().getDomain()
- frm=stanza.getFrom()
- if frm.getDomain():
- frm=frm.getDomain()
- route=Protocol('route', to=to, frm=frm, payload=[stanza])
- stanza=route
- stanza.setNamespace(self._owner.Namespace)
- stanza.setParent(self._metastream)
- if is_message:
- self._owner.Connection.send(stanza, True, now = now)
- else:
- self._owner.Connection.send(stanza, now = now)
- return _ID
+ def send(self, stanza, now=False):
+ id = None
+ if type(stanza) not in [type(''), type(u'')]:
+ if isinstance(stanza, Protocol):
+ id = stanza.getID()
+ if id is None:
+ stanza.setID(self.getAnID())
+ id = stanza.getID()
+ if self._owner._registered_name and not stanza.getAttr('from'):
+ stanza.setAttr('from', self._owner._registered_name)
+ self._owner.Connection.send(stanza, now)
+ return id
- def disconnect(self):
+class BOSHDispatcher(XMPPDispatcher):
+
+ def PlugIn(self, owner, after_SASL=False, old_features=None):
+ self.old_features = old_features
+ self.after_SASL = after_SASL
+ XMPPDispatcher.PlugIn(self, owner)
+
+ def StreamInit(self):
+ ''' Send an initial stream header. '''
+ self.Stream = simplexml.NodeBuilder()
+ self.Stream.dispatch = self.dispatch
+ self.Stream._dispatch_depth = 2
+ self.Stream.stream_header_received = self._check_stream_start
+ self.Stream.features = self.old_features
+
+ self._metastream = Node('stream:stream')
+ self._metastream.setNamespace(self._owner.Namespace)
+ self._metastream.setAttr('version', '1.0')
+ self._metastream.setAttr('xmlns:stream', NS_STREAMS)
+ self._metastream.setAttr('to', self._owner.Server)
+ if locale.getdefaultlocale()[0]:
+ self._metastream.setAttr('xml:lang',
+ locale.getdefaultlocale()[0].split('_')[0])
+
+ self.restart = True
+ self._owner.Connection.send_init(after_SASL=self.after_SASL)
+
+
+ def StreamTerminate(self):
''' Send a stream terminator. '''
- self._owner.Connection.send('')
+ self._owner.Connection.send_terminator()
+
+ def ProcessNonBlocking(self, data=None):
+
+ if self.restart:
+ fromstream = self._metastream
+ fromstream.setAttr('from', fromstream.getAttr('to'))
+ fromstream.delAttr('to')
+ data = '%s%s>%s' % (XML_DECLARATION,str(fromstream)[:-2] ,data)
+ self.restart = False
+
+ return XMPPDispatcher.ProcessNonBlocking(self, data)
+
+ def dispatch(self, stanza, session=None, direct=0):
+
+ if stanza.getName()=='body' and stanza.getNamespace()==NS_HTTP_BIND:
+
+ stanza_attrs = stanza.getAttrs()
+ if stanza_attrs.has_key('authid'):
+ # should be only in init response
+ # auth module expects id of stream in document attributes
+ self.Stream._document_attrs['id'] = stanza_attrs['authid']
+
+ self._owner.Connection.handle_body_attrs(stanza_attrs)
+
+ children = stanza.getChildren()
+
+ if children:
+ for child in children:
+ # if child doesn't have any ns specified, simplexml (or expat) thinks it's
+ # of parent's (BOSH body) namespace, so we have to rewrite it to
+ # jabber:client
+ if child.getNamespace() == NS_HTTP_BIND:
+ child.setNamespace(self._owner.defaultNamespace)
+ XMPPDispatcher.dispatch(self, child, session, direct)
+ else:
+ XMPPDispatcher.dispatch(self, stanza, session, direct)
# vim: se ts=3:
diff --git a/src/common/xmpp/features.py b/src/common/xmpp/features.py
deleted file mode 100644
index 4aac70218..000000000
--- a/src/common/xmpp/features.py
+++ /dev/null
@@ -1,188 +0,0 @@
-## features.py
-##
-## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
-##
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
-
-"""
-This module contains variable stuff that is not worth splitting into separate modules.
-Here is:
- DISCO client and agents-to-DISCO and browse-to-DISCO emulators.
- IBR and password manager.
- jabber:iq:privacy methods
-All these methods takes 'disp' first argument that should be already connected
-(and in most cases already authorised) dispatcher instance.
-"""
-
-from protocol import *
-
-REGISTER_DATA_RECEIVED='REGISTER DATA RECEIVED'
-PRIVACY_LISTS_RECEIVED='PRIVACY LISTS RECEIVED'
-PRIVACY_LIST_RECEIVED='PRIVACY LIST RECEIVED'
-PRIVACY_LISTS_ACTIVE_DEFAULT='PRIVACY LISTS ACTIVE DEFAULT'
-
-### DISCO ### http://jabber.org/protocol/disco ### JEP-0030 ####################
-### Browse ### jabber:iq:browse ### JEP-0030 ###################################
-### Agents ### jabber:iq:agents ### JEP-0030 ###################################
-def _discover(disp,ns,jid,node=None,fb2b=0,fb2a=1):
- """ Try to obtain info from the remote object.
- If remote object doesn't support disco fall back to browse (if fb2b is true)
- and if it doesnt support browse (or fb2b is not true) fall back to agents protocol
- (if gb2a is true). Returns obtained info. Used internally. """
- iq=Iq(to=jid,typ='get',queryNS=ns)
- if node: iq.setQuerynode(node)
- rep=disp.SendAndWaitForResponse(iq)
- if fb2b and not isResultNode(rep): rep=disp.SendAndWaitForResponse(Iq(to=jid,typ='get',queryNS=NS_BROWSE)) # Fallback to browse
- if fb2a and not isResultNode(rep): rep=disp.SendAndWaitForResponse(Iq(to=jid,typ='get',queryNS=NS_AGENTS)) # Fallback to agents
- if isResultNode(rep): return rep.getQueryPayload()
- return []
-
-def discoverItems(disp,jid,node=None):
- """ Query remote object about any items that it contains. Return items list. """
- # According to JEP-0030:
- # query MAY have node attribute
- # item: MUST HAVE jid attribute and MAY HAVE name, node, action attributes.
- # action attribute of item can be either of remove or update value.
- ret=[]
- for i in _discover(disp,NS_DISCO_ITEMS,jid,node):
- if i.getName()=='agent' and i.getTag('name'): i.setAttr('name',i.getTagData('name'))
- ret.append(i.attrs)
- return ret
-
-def discoverInfo(disp,jid,node=None):
- """ Query remote object about info that it publishes. Returns identities and features lists."""
- # According to JEP-0030:
- # query MAY have node attribute
- # identity: MUST HAVE category and name attributes and MAY HAVE type attribute.
- # feature: MUST HAVE var attribute"""
- identities , features = [] , []
- for i in _discover(disp,NS_DISCO_INFO,jid,node):
- if i.getName()=='identity': identities.append(i.attrs)
- elif i.getName()=='feature': features.append(i.getAttr('var'))
- elif i.getName()=='agent':
- if i.getTag('name'): i.setAttr('name',i.getTagData('name'))
- if i.getTag('description'): i.setAttr('name',i.getTagData('description'))
- identities.append(i.attrs)
- if i.getTag('groupchat'): features.append(NS_GROUPCHAT)
- if i.getTag('register'): features.append(NS_REGISTER)
- if i.getTag('search'): features.append(NS_SEARCH)
- return identities , features
-
-### Registration ### jabber:iq:register ### JEP-0077 ###########################
-def getRegInfo(disp,host,info={},sync=True):
- """ Gets registration form from remote host.
- You can pre-fill the info dictionary.
- F.e. if you are requesting info on registering user joey than specify
- info as {'username':'joey'}. See JEP-0077 for details.
- 'disp' must be connected dispatcher instance."""
- iq=Iq('get',NS_REGISTER,to=host)
- for i in info.keys(): iq.setTagData(i,info[i])
- if sync:
- resp=disp.SendAndWaitForResponse(iq)
- _ReceivedRegInfo(disp.Dispatcher,resp, host)
- else: disp.SendAndCallForResponse(iq,_ReceivedRegInfo, {'agent': host})
-
-def _ReceivedRegInfo(con, resp, agent):
- iq=Iq('get',NS_REGISTER,to=agent)
- if not isResultNode(resp): return
- df=resp.getTag('query',namespace=NS_REGISTER).getTag('x',namespace=NS_DATA)
- if df:
- con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,DataForm(node=df),True))
- return
- df=DataForm(typ='form')
- for i in resp.getQueryPayload():
- if type(i)!=type(iq): pass
- elif i.getName()=='instructions': df.addInstructions(i.getData())
- else: df.setField(i.getName()).setValue(i.getData())
- con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,df,False))
-
-def register(disp,host,info):
- """ Perform registration on remote server with provided info.
- disp must be connected dispatcher instance.
- Returns true or false depending on registration result.
- If registration fails you can get additional info from the dispatcher's owner
- attributes lastErrNode, lastErr and lastErrCode.
- """
- iq=Iq('set',NS_REGISTER,to=host)
- if not isinstance(info, dict): info=info.asDict()
- for i in info.keys(): iq.setTag('query').setTagData(i,info[i])
- resp=disp.SendAndWaitForResponse(iq)
- if isResultNode(resp): return 1
-
-def unregister(disp,host):
- """ Unregisters with host (permanently removes account).
- disp must be connected and authorized dispatcher instance.
- Returns true on success."""
- resp=disp.SendAndWaitForResponse(Iq('set',NS_REGISTER,to=host,payload=[Node('remove')]))
- if isResultNode(resp): return 1
-
-def changePasswordTo(disp,newpassword,host=None):
- """ Changes password on specified or current (if not specified) server.
- disp must be connected and authorized dispatcher instance.
- Returns true on success."""
- if not host: host=disp._owner.Server
- resp=disp.SendAndWaitForResponse(Iq('set',NS_REGISTER,to=host,payload=[Node('username',payload=[disp._owner.Server]),Node('password',payload=[newpassword])]))
- if isResultNode(resp): return 1
-
-### Privacy ### jabber:iq:privacy ### draft-ietf-xmpp-im-19 ####################
-#type=[jid|group|subscription]
-#action=[allow|deny]
-
-def getPrivacyLists(disp):
- """ Requests privacy lists from connected server.
- Returns dictionary of existing lists on success."""
- try:
- dict_={'lists':[]}
- resp=disp.SendAndWaitForResponse(Iq('get',NS_PRIVACY))
- if not isResultNode(resp): return
- for list_ in resp.getQueryPayload():
- if list_.getName()=='list': dict_['lists'].append(list_.getAttr('name'))
- else: dict_[list_.getName()]=list_.getAttr('name')
- return dict_
- except Exception:
- pass
-
-def getPrivacyList(disp,listname):
- """ Requests specific privacy list listname. Returns list of XML nodes (rules)
- taken from the server responce."""
- try:
- resp=disp.SendAndWaitForResponse(Iq('get',NS_PRIVACY,payload=[Node('list',{'name':listname})]))
- if isResultNode(resp): return resp.getQueryPayload()[0]
- except Exception:
- pass
-
-def setActivePrivacyList(disp,listname=None,typ='active'):
- """ Switches privacy list 'listname' to specified type.
- By default the type is 'active'. Returns true on success."""
- if listname: attrs={'name':listname}
- else: attrs={}
- resp=disp.SendAndWaitForResponse(Iq('set',NS_PRIVACY,payload=[Node(typ,attrs)]))
- if isResultNode(resp): return 1
-
-def setDefaultPrivacyList(disp,listname=None):
- """ Sets the default privacy list as 'listname'. Returns true on success."""
- return setActivePrivacyList(disp,listname,'default')
-
-def setPrivacyList(disp,list_):
- """ Set the ruleset. 'list' should be the simpleXML node formatted
- according to RFC 3921 (XMPP-IM) (I.e. Node('list',{'name':listname},payload=[...]) )
- Returns true on success."""
- resp=disp.SendAndWaitForResponse(Iq('set',NS_PRIVACY,payload=[list_]))
- if isResultNode(resp): return 1
-
-def delPrivacyList(disp,listname):
- """ Deletes privacy list 'listname'. Returns true on success."""
- resp=disp.SendAndWaitForResponse(Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})]))
- if isResultNode(resp): return 1
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/features_nb.py b/src/common/xmpp/features_nb.py
index 5c7b93229..4e1475329 100644
--- a/src/common/xmpp/features_nb.py
+++ b/src/common/xmpp/features_nb.py
@@ -15,9 +15,13 @@
# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
-from features import REGISTER_DATA_RECEIVED, PRIVACY_LISTS_RECEIVED, PRIVACY_LIST_RECEIVED, PRIVACY_LISTS_ACTIVE_DEFAULT
from protocol import *
+REGISTER_DATA_RECEIVED='REGISTER DATA RECEIVED'
+PRIVACY_LISTS_RECEIVED='PRIVACY LISTS RECEIVED'
+PRIVACY_LIST_RECEIVED='PRIVACY LIST RECEIVED'
+PRIVACY_LISTS_ACTIVE_DEFAULT='PRIVACY LISTS ACTIVE DEFAULT'
+
def _on_default_response(disp, iq, cb):
def _on_response(resp):
if isResultNode(resp):
@@ -28,10 +32,10 @@ def _on_default_response(disp, iq, cb):
disp.SendAndCallForResponse(iq, _on_response)
def _discover(disp, ns, jid, node = None, fb2b=0, fb2a=1, cb=None):
- """ Try to obtain info from the remote object.
+ ''' Try to obtain info from the remote object.
If remote object doesn't support disco fall back to browse (if fb2b is true)
and if it doesnt support browse (or fb2b is not true) fall back to agents protocol
- (if gb2a is true). Returns obtained info. Used internally. """
+ (if gb2a is true). Returns obtained info. Used internally. '''
iq=Iq(to=jid, typ='get', queryNS=ns)
if node:
iq.setQuerynode(node)
@@ -57,7 +61,7 @@ def _discover(disp, ns, jid, node = None, fb2b=0, fb2a=1, cb=None):
# this function is not used in gajim ???
def discoverItems(disp,jid,node=None, cb=None):
- """ Query remote object about any items that it contains. Return items list. """
+ ''' Query remote object about any items that it contains. Return items list. '''
# According to JEP-0030:
# query MAY have node attribute
# item: MUST HAVE jid attribute and MAY HAVE name, node, action attributes.
@@ -74,7 +78,7 @@ def discoverItems(disp,jid,node=None, cb=None):
# this one is
def discoverInfo(disp,jid,node=None, cb=None):
- """ Query remote object about info that it publishes. Returns identities and features lists."""
+ ''' Query remote object about info that it publishes. Returns identities and features lists.'''
# According to JEP-0030:
# query MAY have node attribute
# identity: MUST HAVE category and name attributes and MAY HAVE type attribute.
@@ -104,11 +108,11 @@ def discoverInfo(disp,jid,node=None, cb=None):
### Registration ### jabber:iq:register ### JEP-0077 ###########################
def getRegInfo(disp, host, info={}, sync=True):
- """ Gets registration form from remote host.
+ ''' Gets registration form from remote host.
You can pre-fill the info dictionary.
F.e. if you are requesting info on registering user joey than specify
info as {'username':'joey'}. See JEP-0077 for details.
- 'disp' must be connected dispatcher instance."""
+ 'disp' must be connected dispatcher instance.'''
iq=Iq('get',NS_REGISTER,to=host)
for i in info.keys():
iq.setTagData(i,info[i])
@@ -140,11 +144,11 @@ def _ReceivedRegInfo(con, resp, agent):
con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,''))
def register(disp, host, info, cb):
- """ Perform registration on remote server with provided info.
+ ''' Perform registration on remote server with provided info.
disp must be connected dispatcher instance.
If registration fails you can get additional info from the dispatcher's owner
attributes lastErrNode, lastErr and lastErrCode.
- """
+ '''
iq=Iq('set', NS_REGISTER, to=host)
if not isinstance(info, dict):
info=info.asDict()
@@ -153,16 +157,16 @@ def register(disp, host, info, cb):
disp.SendAndCallForResponse(iq, cb)
def unregister(disp, host, cb):
- """ Unregisters with host (permanently removes account).
+ ''' Unregisters with host (permanently removes account).
disp must be connected and authorized dispatcher instance.
- Returns true on success."""
+ Returns true on success.'''
iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')])
_on_default_response(disp, iq, cb)
def changePasswordTo(disp, newpassword, host=None, cb = None):
- """ Changes password on specified or current (if not specified) server.
+ ''' Changes password on specified or current (if not specified) server.
disp must be connected and authorized dispatcher instance.
- Returns true on success."""
+ Returns true on success.'''
if not host: host=disp._owner.Server
iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username',
payload=[disp._owner.Server]),Node('password',payload=[newpassword])])
@@ -173,8 +177,8 @@ def changePasswordTo(disp, newpassword, host=None, cb = None):
#action=[allow|deny]
def getPrivacyLists(disp):
- """ Requests privacy lists from connected server.
- Returns dictionary of existing lists on success."""
+ ''' Requests privacy lists from connected server.
+ Returns dictionary of existing lists on success.'''
iq = Iq('get', NS_PRIVACY)
def _on_response(resp):
dict_ = {'lists': []}
@@ -205,8 +209,8 @@ def getActiveAndDefaultPrivacyLists(disp):
disp.SendAndCallForResponse(iq, _on_response)
def getPrivacyList(disp, listname):
- """ Requests specific privacy list listname. Returns list of XML nodes (rules)
- taken from the server responce."""
+ ''' Requests specific privacy list listname. Returns list of XML nodes (rules)
+ taken from the server responce.'''
def _on_response(resp):
if not isResultNode(resp):
disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False))
@@ -216,8 +220,8 @@ def getPrivacyList(disp, listname):
disp.SendAndCallForResponse(iq, _on_response)
def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
- """ Switches privacy list 'listname' to specified type.
- By default the type is 'active'. Returns true on success."""
+ ''' Switches privacy list 'listname' to specified type.
+ By default the type is 'active'. Returns true on success.'''
if listname:
attrs={'name':listname}
else:
@@ -226,13 +230,13 @@ def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
_on_default_response(disp, iq, cb)
def setDefaultPrivacyList(disp, listname=None):
- """ Sets the default privacy list as 'listname'. Returns true on success."""
+ ''' Sets the default privacy list as 'listname'. Returns true on success.'''
return setActivePrivacyList(disp, listname,'default')
def setPrivacyList(disp, listname, tags):
- """ Set the ruleset. 'list' should be the simpleXML node formatted
+ ''' Set the ruleset. 'list' should be the simpleXML node formatted
according to RFC 3921 (XMPP-IM) (I.e. Node('list',{'name':listname},payload=[...]) )
- Returns true on success."""
+ Returns true on success.'''
iq = Iq('set', NS_PRIVACY, xmlns = '')
list_query = iq.getTag('query').setTag('list', {'name': listname})
for item in tags:
@@ -248,7 +252,7 @@ def setPrivacyList(disp, listname, tags):
_on_default_response(disp, iq, None)
def delPrivacyList(disp,listname,cb=None):
- """ Deletes privacy list 'listname'. Returns true on success."""
+ ''' Deletes privacy list 'listname'. Returns true on success.'''
iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})])
_on_default_response(disp, iq, cb)
diff --git a/src/common/xmpp/filetransfer.py b/src/common/xmpp/filetransfer.py
deleted file mode 100644
index d0359d3ec..000000000
--- a/src/common/xmpp/filetransfer.py
+++ /dev/null
@@ -1,204 +0,0 @@
-## filetransfer.py
-##
-## Copyright (C) 2004 Alexey "Snake" Nezhdanov
-##
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-# $Id: filetransfer.py,v 1.6 2004/12/25 20:06:59 snakeru Exp $
-
-"""
-This module contains IBB class that is the simple implementation of JEP-0047.
-Note that this is just a transport for data. You have to negotiate data transfer before
-(via StreamInitiation most probably). Unfortunately SI is not implemented yet.
-"""
-
-from protocol import *
-from dispatcher import PlugIn
-import base64
-
-class IBB(PlugIn):
- """ IBB used to transfer small-sized data chunk over estabilished xmpp connection.
- Data is split into small blocks (by default 3000 bytes each), encoded as base 64
- and sent to another entity that compiles these blocks back into the data chunk.
- This is very inefficiend but should work under any circumstances. Note that
- using IBB normally should be the last resort.
- """
- def __init__(self):
- """ Initialise internal variables. """
- PlugIn.__init__(self)
- self.DBG_LINE='ibb'
- self._exported_methods=[self.OpenStream]
- self._streams={}
- self._ampnode=Node(NS_AMP+' amp',payload=[Node('rule',{'condition':'deliver-at','value':'stored','action':'error'}),Node('rule',{'condition':'match-resource','value':'exact','action':'error'})])
-
- def plugin(self,owner):
- """ Register handlers for receiving incoming datastreams. Used internally. """
- self._owner.RegisterHandlerOnce('iq',self.StreamOpenReplyHandler) # Move to StreamOpen and specify stanza id
- self._owner.RegisterHandler('iq',self.IqHandler,ns=NS_IBB)
- self._owner.RegisterHandler('message',self.ReceiveHandler,ns=NS_IBB)
-
- def IqHandler(self,conn,stanza):
- """ Handles streams state change. Used internally. """
- typ=stanza.getType()
- self.DEBUG('IqHandler called typ->%s'%typ,'info')
- if typ=='set' and stanza.getTag('open',namespace=NS_IBB): self.StreamOpenHandler(conn,stanza)
- elif typ=='set' and stanza.getTag('close',namespace=NS_IBB): self.StreamCloseHandler(conn,stanza)
- elif typ=='result': self.StreamCommitHandler(conn,stanza)
- elif typ=='error': self.StreamOpenReplyHandler(conn,stanza)
- else: conn.send(Error(stanza,ERR_BAD_REQUEST))
- raise NodeProcessed
-
- def StreamOpenHandler(self,conn,stanza):
- """ Handles opening of new incoming stream. Used internally. """
- #
- #
- #
- err=None
- sid,blocksize=stanza.getTagAttr('open','sid'),stanza.getTagAttr('open','block-size')
- self.DEBUG('StreamOpenHandler called sid->%s blocksize->%s'%(sid,blocksize),'info')
- try:
- blocksize=int(blocksize)
- except Exception:
- err=ERR_BAD_REQUEST
- if not sid or not blocksize: err=ERR_BAD_REQUEST
- elif sid in self._streams.keys(): err=ERR_UNEXPECTED_REQUEST
- if err: rep=Error(stanza,err)
- else:
- self.DEBUG("Opening stream: id %s, block-size %s"%(sid,blocksize),'info')
- rep=Protocol('iq',stanza.getFrom(),'result',stanza.getTo(),{'id':stanza.getID()})
- self._streams[sid]={'direction':'<'+str(stanza.getFrom()),'block-size':blocksize,'fp':open('/tmp/xmpp_file_'+sid,'w'),'seq':0,'syn_id':stanza.getID()}
- conn.send(rep)
-
- def OpenStream(self,sid,to,fp,blocksize=3000):
- """ Start new stream. You should provide stream id 'sid', the endpoind jid 'to',
- the file object containing info for send 'fp'. Also the desired blocksize can be specified.
- Take into account that recommended stanza size is 4k and IBB uses base64 encoding
- that increases size of data by 1/3."""
- if sid in self._streams.keys(): return
- if not JID(to).getResource(): return
- self._streams[sid]={'direction':'|>'+to,'block-size':blocksize,'fp':fp,'seq':0}
- self._owner.RegisterCycleHandler(self.SendHandler)
- syn=Protocol('iq',to,'set',payload=[Node(NS_IBB+' open',{'sid':sid,'block-size':blocksize})])
- self._owner.send(syn)
- self._streams[sid]['syn_id']=syn.getID()
- return self._streams[sid]
-
- def SendHandler(self,conn):
- """ Send next portion of data if it is time to do it. Used internally. """
- self.DEBUG('SendHandler called','info')
- for sid in self._streams.keys():
- stream=self._streams[sid]
- if stream['direction'][:2]=='|>':
- pass
- elif stream['direction'][0]=='>':
- chunk=stream['fp'].read(stream['block-size'])
- if chunk:
- datanode=Node(NS_IBB+' data',{'sid':sid,'seq':stream['seq']},base64.encodestring(chunk))
- stream['seq']+=1
- if stream['seq']==65536: stream['seq']=0
- conn.send(Protocol('message',stream['direction'][1:],payload=[datanode,self._ampnode]))
- else:
- # notify the other side about stream closing
- # notify the local user about sucessfull send
- # delete the local stream
- conn.send(Protocol('iq',stream['direction'][1:],'set',payload=[Node(NS_IBB+' close',{'sid':sid})]))
- conn.Event(self.DBG_LINE,'SUCCESSFULL SEND',stream)
- del self._streams[sid]
- self._owner.UnregisterCycleHandler(self.SendHandler)
-
-#
-#
-# qANQR1DBwU4DX7jmYZnncmUQB/9KuKBddzQH+tZ1ZywKK0yHKnq57kWq+RFtQdCJ
-# WpdWpR0uQsuJe7+vh3NWn59/gTc5MDlX8dS9p0ovStmNcyLhxVgmqS8ZKhsblVeu
-# IpQ0JgavABqibJolc3BKrVtVV1igKiX/N7Pi8RtY1K18toaMDhdEfhBRzO/XB0+P
-# AQhYlRjNacGcslkhXqNjK5Va4tuOAPy2n1Q8UUrHbUd0g+xJ9Bm0G0LZXyvCWyKH
-# kuNEHFQiLuCY6Iv0myq6iX6tjuHehZlFSh80b5BVV9tNLwNR5Eqz1klxMhoghJOA
-#
-#
-#
-#
-#
-#
-
- def ReceiveHandler(self,conn,stanza):
- """ Receive next portion of incoming datastream and store it write
- it to temporary file. Used internally.
- """
- sid,seq,data=stanza.getTagAttr('data','sid'),stanza.getTagAttr('data','seq'),stanza.getTagData('data')
- self.DEBUG('ReceiveHandler called sid->%s seq->%s'%(sid,seq),'info')
- try:
- seq=int(seq)
- data=base64.decodestring(data)
- except Exception:
- seq=''
- data=''
- err=None
- if not sid in self._streams.keys(): err=ERR_ITEM_NOT_FOUND
- else:
- stream=self._streams[sid]
- if not data: err=ERR_BAD_REQUEST
- elif seq!=stream['seq']: err=ERR_UNEXPECTED_REQUEST
- else:
- self.DEBUG('Successfull receive sid->%s %s+%s bytes'%(sid,stream['fp'].tell(),len(data)),'ok')
- stream['seq']+=1
- stream['fp'].write(data)
- if err:
- self.DEBUG('Error on receive: %s'%err,'error')
- conn.send(Error(Iq(to=stanza.getFrom(),frm=stanza.getTo(),payload=[Node(NS_IBB+' close')]),err,reply=0))
-
- def StreamCloseHandler(self,conn,stanza):
- """ Handle stream closure due to all data transmitted.
- Raise xmpppy event specifying successfull data receive. """
- sid=stanza.getTagAttr('close','sid')
- self.DEBUG('StreamCloseHandler called sid->%s'%sid,'info')
- if sid in self._streams.keys():
- conn.send(stanza.buildReply('result'))
- conn.Event(self.DBG_LINE,'SUCCESSFULL RECEIVE',self._streams[sid])
- del self._streams[sid]
- else: conn.send(Error(stanza,ERR_ITEM_NOT_FOUND))
-
- def StreamBrokenHandler(self,conn,stanza):
- """ Handle stream closure due to all some error while receiving data.
- Raise xmpppy event specifying unsuccessfull data receive. """
- syn_id=stanza.getID()
- self.DEBUG('StreamBrokenHandler called syn_id->%s'%syn_id,'info')
- for sid in self._streams.keys():
- stream=self._streams[sid]
- if stream['syn_id']==syn_id:
- if stream['direction'][0]=='<': conn.Event(self.DBG_LINE,'ERROR ON RECEIVE',stream)
- else: conn.Event(self.DBG_LINE,'ERROR ON SEND',stream)
- del self._streams[sid]
-
- def StreamOpenReplyHandler(self,conn,stanza):
- """ Handle remote side reply about is it agree or not to receive our datastream.
- Used internally. Raises xmpppy event specfiying if the data transfer
- is agreed upon."""
- syn_id=stanza.getID()
- self.DEBUG('StreamOpenReplyHandler called syn_id->%s'%syn_id,'info')
- for sid in self._streams.keys():
- stream=self._streams[sid]
- if stream['syn_id']==syn_id:
- if stanza.getType()=='error':
- if stream['direction'][0]=='<': conn.Event(self.DBG_LINE,'ERROR ON RECEIVE',stream)
- else: conn.Event(self.DBG_LINE,'ERROR ON SEND',stream)
- del self._streams[sid]
- elif stanza.getType()=='result':
- if stream['direction'][0]=='|':
- stream['direction']=stream['direction'][1:]
- conn.Event(self.DBG_LINE,'STREAM COMMITTED',stream)
- else: conn.send(Error(stanza,ERR_UNEXPECTED_REQUEST))
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py
index b26079097..9476396fa 100644
--- a/src/common/xmpp/idlequeue.py
+++ b/src/common/xmpp/idlequeue.py
@@ -13,6 +13,8 @@
## GNU General Public License for more details.
import select
+import logging
+log = logging.getLogger('gajim.c.x.idlequeue')
class IdleObject:
''' base class for all idle listeners, these are the methods, which are called from IdleQueue
@@ -32,8 +34,8 @@ class IdleObject:
''' called on new write event (connect in sockets is a pollout) '''
pass
- def read_timeout(self, fd):
- ''' called when timeout has happend '''
+ def read_timeout(self):
+ ''' called when timeout happened '''
pass
class IdleQueue:
@@ -52,6 +54,7 @@ class IdleQueue:
self.selector = select.poll()
def remove_timeout(self, fd):
+ log.info('read timeout removed for fd %s' % fd)
if fd in self.read_timeouts:
del(self.read_timeouts[fd])
@@ -63,19 +66,38 @@ class IdleQueue:
self.alarms[alarm_time].append(alarm_cb)
else:
self.alarms[alarm_time] = [alarm_cb]
+ return alarm_time
+
+ def remove_alarm(self, alarm_cb, alarm_time):
+ ''' removes alarm callback alarm_cb scheduled on alarm_time'''
+ if not self.alarms.has_key(alarm_time): return False
+ i = -1
+ for i in range(len(self.alarms[alarm_time])):
+ # let's not modify the list inside the loop
+ if self.alarms[alarm_time][i] is alarm_cb: break
+ if i != -1:
+ del self.alarms[alarm_time][i]
+ if self.alarms[alarm_time] == []:
+ del self.alarms[alarm_time]
+ return True
+ else:
+ return False
def set_read_timeout(self, fd, seconds):
''' set a new timeout, if it is not removed after 'seconds',
then obj.read_timeout() will be called '''
+ log.info('read timeout set for fd %s on %s seconds' % (fd, seconds))
timeout = self.current_time() + seconds
self.read_timeouts[fd] = timeout
def check_time_events(self):
+ log.info('check time evs')
current_time = self.current_time()
for fd, timeout in self.read_timeouts.items():
if timeout > current_time:
continue
if fd in self.queue:
+ log.debug('Calling read_timeout for fd %s' % fd)
self.queue[fd].read_timeout()
else:
self.remove_timeout(fd)
@@ -83,9 +105,9 @@ class IdleQueue:
for alarm_time in times:
if alarm_time > current_time:
break
- for cb in self.alarms[alarm_time]:
- cb()
- del(self.alarms[alarm_time])
+ if self.alarms.has_key(alarm_time):
+ for cb in self.alarms[alarm_time]: cb()
+ if self.alarms.has_key(alarm_time): del(self.alarms[alarm_time])
def plug_idle(self, obj, writable = True, readable = True):
if obj.fd == -1:
@@ -128,6 +150,7 @@ class IdleQueue:
return False
if flags & 3: # waiting read event
+ #print 'waiting read on %d, flags are %d' % (fd, flags)
obj.pollin()
return True
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index 00ad39983..a45936506 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -14,109 +14,109 @@
# $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $
-"""
+'''
Protocol module contains tools that is needed for processing of
xmpp-related data structures.
-"""
+'''
from simplexml import Node, NodeBuilder
import time
-NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108
-NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033
-NS_AGENTS ='jabber:iq:agents'
-NS_AMP ='http://jabber.org/protocol/amp'
+NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108
+NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033
+NS_AGENTS ='jabber:iq:agents'
+NS_AMP ='http://jabber.org/protocol/amp'
NS_AMP_ERRORS =NS_AMP+'#errors'
-NS_AUTH ='jabber:iq:auth'
-NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata'
-NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
-NS_BROWSE ='jabber:iq:browse'
-NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195
-NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065
-NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115
-NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085
-NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194
-NS_CLIENT ='jabber:client'
-NS_COMMANDS ='http://jabber.org/protocol/commands'
+NS_AUTH ='jabber:iq:auth'
+NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata'
+NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
+NS_BROWSE ='jabber:iq:browse'
+NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195
+NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065
+NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115
+NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085
+NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194
+NS_CLIENT ='jabber:client'
+NS_COMMANDS ='http://jabber.org/protocol/commands'
NS_COMPONENT_ACCEPT='jabber:component:accept'
NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0'
-NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138
+NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138
NS_CONFERENCE ='jabber:x:conference'
-NS_DATA ='jabber:x:data' # XEP-0004
-NS_DELAY ='jabber:x:delay'
-NS_DELAY2 ='urn:xmpp:delay'
-NS_DIALBACK ='jabber:server:dialback'
-NS_DISCO ='http://jabber.org/protocol/disco'
+NS_DATA ='jabber:x:data' # XEP-0004
+NS_DELAY ='jabber:x:delay'
+NS_DELAY2 ='urn:xmpp:delay'
+NS_DIALBACK ='jabber:server:dialback'
+NS_DISCO ='http://jabber.org/protocol/disco'
NS_DISCO_INFO =NS_DISCO+'#info'
NS_DISCO_ITEMS =NS_DISCO+'#items'
-NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027
-NS_ESESSION ='http://www.xmpp.org/extensions/xep-0116.html#ns'
+NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027
+NS_ESESSION ='http://www.xmpp.org/extensions/xep-0116.html#ns'
NS_ESESSION_INIT='http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116
-NS_EVENT ='jabber:x:event' # XEP-0022
-NS_FEATURE ='http://jabber.org/protocol/feature-neg'
-NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096
-NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196
-NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080
-NS_GROUPCHAT ='gc-1.0'
-NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070
-NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124
-NS_IBB ='http://jabber.org/protocol/ibb'
-NS_INVISIBLE ='presence-invisible' # Jabberd2
-NS_IQ ='iq' # Jabberd2
-NS_LAST ='jabber:iq:last'
-NS_MESSAGE ='message' # Jabberd2
-NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107
-NS_MUC ='http://jabber.org/protocol/muc'
-NS_MUC_USER =NS_MUC+'#user'
-NS_MUC_ADMIN =NS_MUC+'#admin'
-NS_MUC_OWNER =NS_MUC+'#owner'
+NS_EVENT ='jabber:x:event' # XEP-0022
+NS_FEATURE ='http://jabber.org/protocol/feature-neg'
+NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096
+NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196
+NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080
+NS_GROUPCHAT ='gc-1.0'
+NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070
+NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124
+NS_IBB ='http://jabber.org/protocol/ibb'
+NS_INVISIBLE ='presence-invisible' # Jabberd2
+NS_IQ ='iq' # Jabberd2
+NS_LAST ='jabber:iq:last'
+NS_MESSAGE ='message' # Jabberd2
+NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107
+NS_MUC ='http://jabber.org/protocol/muc'
+NS_MUC_USER =NS_MUC+'#user'
+NS_MUC_ADMIN =NS_MUC+'#admin'
+NS_MUC_OWNER =NS_MUC+'#owner'
NS_MUC_UNIQUE =NS_MUC+'#unique'
NS_MUC_CONFIG =NS_MUC+'#roomconfig'
-NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172
-NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # XEP-0013
-NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112
-NS_PING ='urn:xmpp:ping' # SEP-0199
-NS_PRESENCE ='presence' # Jabberd2
-NS_PRIVACY ='jabber:iq:privacy'
-NS_PRIVATE ='jabber:iq:private'
-NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154
-NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060
-NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060
-NS_REGISTER ='jabber:iq:register'
-NS_ROSTER ='jabber:iq:roster'
-NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144
-NS_RPC ='jabber:iq:rpc' # XEP-0009
-NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl'
-NS_SEARCH ='jabber:iq:search'
-NS_SERVER ='jabber:server'
-NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session'
-NS_SI ='http://jabber.org/protocol/si' # XEP-0096
-NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137
-NS_SIGNED ='jabber:x:signed' # XEP-0027
-NS_SSN ='urn:xmpp:ssn' # XEP-0155
-NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200
-NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
-NS_STREAM ='http://affinix.com/jabber/stream'
-NS_STREAMS ='http://etherx.jabber.org/streams'
-NS_TIME ='jabber:iq:time' # XEP-0900
-NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202
-NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
-NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118
-NS_VACATION ='http://jabber.org/protocol/vacation'
-NS_VCARD ='vcard-temp'
+NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172
+NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # XEP-0013
+NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112
+NS_PING ='urn:xmpp:ping' # SEP-0199
+NS_PRESENCE ='presence' # Jabberd2
+NS_PRIVACY ='jabber:iq:privacy'
+NS_PRIVATE ='jabber:iq:private'
+NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154
+NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060
+NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060
+NS_REGISTER ='jabber:iq:register'
+NS_ROSTER ='jabber:iq:roster'
+NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144
+NS_RPC ='jabber:iq:rpc' # XEP-0009
+NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl'
+NS_SEARCH ='jabber:iq:search'
+NS_SERVER ='jabber:server'
+NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session'
+NS_SI ='http://jabber.org/protocol/si' # XEP-0096
+NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137
+NS_SIGNED ='jabber:x:signed' # XEP-0027
+NS_SSN ='urn:xmpp:ssn' # XEP-0155
+NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200
+NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
+NS_STREAM ='http://affinix.com/jabber/stream'
+NS_STREAMS ='http://etherx.jabber.org/streams'
+NS_TIME ='jabber:iq:time' # XEP-0900
+NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202
+NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
+NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118
+NS_VACATION ='http://jabber.org/protocol/vacation'
+NS_VCARD ='vcard-temp'
NS_GMAILNOTIFY ='google:mail:notify'
NS_GTALKSETTING ='google:setting'
NS_VCARD_UPDATE =NS_VCARD+':x:update'
-NS_VERSION ='jabber:iq:version'
-NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197
-NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130
-NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071
-NS_XHTML = 'http://www.w3.org/1999/xhtml' # "
-NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141
-NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122
+NS_VERSION ='jabber:iq:version'
+NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197
+NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130
+NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071
+NS_XHTML = 'http://www.w3.org/1999/xhtml' # "
+NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141
+NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122
NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams'
NS_RECEIPTS ='urn:xmpp:receipts'
-xmpp_stream_error_conditions="""
+xmpp_stream_error_conditions='''
bad-format -- -- -- The entity has sent XML that cannot be processed.
bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.
conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.
@@ -140,8 +140,8 @@ undefined-condition -- -- -- The error condition is not one of those defined b
unsupported-encoding -- -- -- The initiating entity has encoded the stream in an encoding that is not supported by the server.
unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server.
unsupported-version -- -- -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server.
-xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed."""
-xmpp_stanza_error_conditions="""
+xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed.'''
+xmpp_stanza_error_conditions='''
bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed.
conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address.
feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed.
@@ -163,39 +163,39 @@ resource-constraint -- 500 -- wait -- The server or recipient lacks the system r
service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service.
subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required.
undefined-condition -- 500 -- -- Undefined Condition
-unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order)."""
-sasl_error_conditions="""
+unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).'''
+sasl_error_conditions='''
aborted -- -- -- The receiving entity acknowledges an element sent by the initiating entity; sent in reply to the element.
incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a element or an element with initial response data.
invalid-authzid -- -- -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a element or an element with initial response data.
invalid-mechanism -- -- -- The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an element.
mechanism-too-weak -- -- -- The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a element or an element with initial response data.
not-authorized -- -- -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a element or an element with initial response data.
-temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an element or element."""
+temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an element or element.'''
ERRORS,_errorcodes={},{}
for ns,errname,errpool in ((NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions),
- (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions),
- (NS_SASL ,'SASL' ,sasl_error_conditions)):
- for err in errpool.split('\n')[1:]:
- cond,code,typ,text=err.split(' -- ')
- name=errname+'_'+cond.upper().replace('-','_')
- locals()[name]=ns+' '+cond
- ERRORS[ns+' '+cond]=[code,typ,text]
- if code: _errorcodes[code]=cond
+ (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions),
+ (NS_SASL ,'SASL' ,sasl_error_conditions)):
+ for err in errpool.split('\n')[1:]:
+ cond,code,typ,text=err.split(' -- ')
+ name=errname+'_'+cond.upper().replace('-','_')
+ locals()[name]=ns+' '+cond
+ ERRORS[ns+' '+cond]=[code,typ,text]
+ if code: _errorcodes[code]=cond
del ns,errname,errpool,err,cond,code,typ,text
def isResultNode(node):
- """ Returns true if the node is a positive reply. """
- return node and node.getType()=='result'
+ ''' Returns true if the node is a positive reply. '''
+ return node and node.getType()=='result'
def isErrorNode(node):
- """ Returns true if the node is a negative reply. """
- return node and node.getType()=='error'
+ ''' Returns true if the node is a negative reply. '''
+ return node and node.getType()=='error'
class NodeProcessed(Exception):
- """ Exception that should be raised by handler when the handling should be stopped. """
+ ''' Exception that should be raised by handler when the handling should be stopped. '''
class StreamError(Exception):
- """ Base exception class for stream errors."""
+ ''' Base exception class for stream errors.'''
class BadFormat(StreamError): pass
class BadNamespacePrefix(StreamError): pass
class Conflict(StreamError): pass
@@ -222,582 +222,589 @@ class UnsupportedVersion(StreamError): pass
class XMLNotWellFormed(StreamError): pass
stream_exceptions = {'bad-format': BadFormat,
- 'bad-namespace-prefix': BadNamespacePrefix,
- 'conflict': Conflict,
- 'connection-timeout': ConnectionTimeout,
- 'host-gone': HostGone,
- 'host-unknown': HostUnknown,
- 'improper-addressing': ImproperAddressing,
- 'internal-server-error': InternalServerError,
- 'invalid-from': InvalidFrom,
- 'invalid-id': InvalidID,
- 'invalid-namespace': InvalidNamespace,
- 'invalid-xml': InvalidXML,
- 'not-authorized': NotAuthorized,
- 'policy-violation': PolicyViolation,
- 'remote-connection-failed': RemoteConnectionFailed,
- 'resource-constraint': ResourceConstraint,
- 'restricted-xml': RestrictedXML,
- 'see-other-host': SeeOtherHost,
- 'system-shutdown': SystemShutdown,
- 'undefined-condition': UndefinedCondition,
- 'unsupported-encoding': UnsupportedEncoding,
- 'unsupported-stanza-type': UnsupportedStanzaType,
- 'unsupported-version': UnsupportedVersion,
- 'xml-not-well-formed': XMLNotWellFormed}
+ 'bad-namespace-prefix': BadNamespacePrefix,
+ 'conflict': Conflict,
+ 'connection-timeout': ConnectionTimeout,
+ 'host-gone': HostGone,
+ 'host-unknown': HostUnknown,
+ 'improper-addressing': ImproperAddressing,
+ 'internal-server-error': InternalServerError,
+ 'invalid-from': InvalidFrom,
+ 'invalid-id': InvalidID,
+ 'invalid-namespace': InvalidNamespace,
+ 'invalid-xml': InvalidXML,
+ 'not-authorized': NotAuthorized,
+ 'policy-violation': PolicyViolation,
+ 'remote-connection-failed': RemoteConnectionFailed,
+ 'resource-constraint': ResourceConstraint,
+ 'restricted-xml': RestrictedXML,
+ 'see-other-host': SeeOtherHost,
+ 'system-shutdown': SystemShutdown,
+ 'undefined-condition': UndefinedCondition,
+ 'unsupported-encoding': UnsupportedEncoding,
+ 'unsupported-stanza-type': UnsupportedStanzaType,
+ 'unsupported-version': UnsupportedVersion,
+ 'xml-not-well-formed': XMLNotWellFormed}
class JID:
- """ JID object. JID can be built from string, modified, compared, serialised into string. """
- def __init__(self, jid=None, node='', domain='', resource=''):
- """ Constructor. JID can be specified as string (jid argument) or as separate parts.
- Examples:
- JID('node@domain/resource')
- JID(node='node',domain='domain.org')
- """
- if not jid and not domain: raise ValueError('JID must contain at least domain name')
- elif isinstance(jid, type(self)): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource
- elif domain: self.node,self.domain,self.resource=node,domain,resource
- else:
- if jid.find('@')+1: self.node,jid=jid.split('@',1)
- else: self.node=''
- if jid.find('/')+1: self.domain,self.resource=jid.split('/',1)
- else: self.domain,self.resource=jid,''
- def getNode(self):
- """ Return the node part of the JID """
- return self.node
- def setNode(self,node):
- """ Set the node part of the JID to new value. Specify None to remove the node part."""
- self.node=node.lower()
- def getDomain(self):
- """ Return the domain part of the JID """
- return self.domain
- def setDomain(self,domain):
- """ Set the domain part of the JID to new value."""
- self.domain=domain.lower()
- def getResource(self):
- """ Return the resource part of the JID """
- return self.resource
- def setResource(self,resource):
- """ Set the resource part of the JID to new value. Specify None to remove the resource part."""
- self.resource=resource
- def getStripped(self):
- """ Return the bare representation of JID. I.e. string value w/o resource. """
- return self.__str__(0)
- def __eq__(self, other):
- """ Compare the JID to another instance or to string for equality. """
- try: other=JID(other)
- except ValueError: return 0
- return self.resource==other.resource and self.__str__(0) == other.__str__(0)
- def __ne__(self, other):
- """ Compare the JID to another instance or to string for non-equality. """
- return not self.__eq__(other)
- def bareMatch(self, other):
- """ Compare the node and domain parts of the JID's for equality. """
- return self.__str__(0) == JID(other).__str__(0)
- def __str__(self,wresource=1):
- """ Serialise JID into string. """
- if self.node: jid=self.node+'@'+self.domain
- else: jid=self.domain
- if wresource and self.resource: return jid+'/'+self.resource
- return jid
- def __hash__(self):
- """ Produce hash of the JID, Allows to use JID objects as keys of the dictionary. """
- return hash(self.__str__())
+ ''' JID object. JID can be built from string, modified, compared, serialised into string. '''
+ def __init__(self, jid=None, node='', domain='', resource=''):
+ ''' Constructor. JID can be specified as string (jid argument) or as separate parts.
+ Examples:
+ JID('node@domain/resource')
+ JID(node='node',domain='domain.org')
+ '''
+ if not jid and not domain: raise ValueError('JID must contain at least domain name')
+ elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource
+ elif domain: self.node,self.domain,self.resource=node,domain,resource
+ else:
+ if jid.find('@')+1: self.node,jid=jid.split('@',1)
+ else: self.node=''
+ if jid.find('/')+1: self.domain,self.resource=jid.split('/',1)
+ else: self.domain,self.resource=jid,''
+ def getNode(self):
+ ''' Return the node part of the JID '''
+ return self.node
+ def setNode(self,node):
+ ''' Set the node part of the JID to new value. Specify None to remove the node part.'''
+ self.node=node.lower()
+ def getDomain(self):
+ ''' Return the domain part of the JID '''
+ return self.domain
+ def setDomain(self,domain):
+ ''' Set the domain part of the JID to new value.'''
+ self.domain=domain.lower()
+ def getResource(self):
+ ''' Return the resource part of the JID '''
+ return self.resource
+ def setResource(self,resource):
+ ''' Set the resource part of the JID to new value. Specify None to remove the resource part.'''
+ self.resource=resource
+ def getStripped(self):
+ ''' Return the bare representation of JID. I.e. string value w/o resource. '''
+ return self.__str__(0)
+ def __eq__(self, other):
+ ''' Compare the JID to another instance or to string for equality. '''
+ try: other=JID(other)
+ except ValueError: return 0
+ return self.resource==other.resource and self.__str__(0) == other.__str__(0)
+ def __ne__(self, other):
+ ''' Compare the JID to another instance or to string for non-equality. '''
+ return not self.__eq__(other)
+ def bareMatch(self, other):
+ ''' Compare the node and domain parts of the JID's for equality. '''
+ return self.__str__(0) == JID(other).__str__(0)
+ def __str__(self,wresource=1):
+ ''' Serialise JID into string. '''
+ if self.node: jid=self.node+'@'+self.domain
+ else: jid=self.domain
+ if wresource and self.resource: return jid+'/'+self.resource
+ return jid
+ def __hash__(self):
+ ''' Produce hash of the JID, Allows to use JID objects as keys of the dictionary. '''
+ return hash(self.__str__())
+
+class BOSHBody(Node):
+ '''
+ tag that wraps usual XMPP stanzas in XMPP over BOSH
+ '''
+ def __init__(self, attrs={}, payload=[], node=None):
+ Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node)
+ self.setNamespace(NS_HTTP_BIND)
+
class Protocol(Node):
- """ A "stanza" object class. Contains methods that are common for presences, iqs and messages. """
- def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
- """ Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'.
- to is the value of 'to' attribure, 'typ' - 'type' attribute
- frn - from attribure, attrs - other attributes mapping, payload - same meaning as for simplexml payload definition
- timestamp - the time value that needs to be stamped over stanza
- xmlns - namespace of top stanza node
- node - parsed or unparsed stana to be taken as prototype.
- """
- if not attrs: attrs={}
- if to: attrs['to']=to
- if frm: attrs['from']=frm
- if typ: attrs['type']=typ
- Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
- if not node and xmlns: self.setNamespace(xmlns)
- if self['to']: self.setTo(self['to'])
- if self['from']: self.setFrom(self['from'])
- if node and isinstance(self, type(node)) and self.__class__==node.__class__ and 'id' in self.attrs: del self.attrs['id']
- self.timestamp=None
- for d in self.getTags('delay',namespace=NS_DELAY2):
- try:
- if d.getAttr('stamp') < self.getTimestamp2():
- self.setTimestamp(d.getAttr('stamp'))
- except Exception:
- pass
- if not self.timestamp:
- for x in self.getTags('x',namespace=NS_DELAY):
- try:
- if x.getAttr('stamp') < self.getTimestamp():
- self.setTimestamp(x.getAttr('stamp'))
- except Exception:
- pass
- if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
- def getTo(self):
- """ Return value of the 'to' attribute. """
- try:
- return self['to']
- except KeyError:
- return None
- def getFrom(self):
- """ Return value of the 'from' attribute. """
- try:
- return self['from']
- except KeyError:
- return None
- def getTimestamp(self):
- """ Return the timestamp in the 'yyyymmddThhmmss' format. """
- if self.timestamp: return self.timestamp
- return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
- def getTimestamp2(self):
- """ Return the timestamp in the 'yyyymmddThhmmss' format. """
- if self.timestamp: return self.timestamp
- return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
- def getID(self):
- """ Return the value of the 'id' attribute. """
- return self.getAttr('id')
- def setTo(self,val):
- """ Set the value of the 'to' attribute. """
- self.setAttr('to', JID(val))
- def getType(self):
- """ Return the value of the 'type' attribute. """
- return self.getAttr('type')
- def setFrom(self,val):
- """ Set the value of the 'from' attribute. """
- self.setAttr('from', JID(val))
- def setType(self,val):
- """ Set the value of the 'type' attribute. """
- self.setAttr('type', val)
- def setID(self,val):
- """ Set the value of the 'id' attribute. """
- self.setAttr('id', val)
- def getError(self):
- """ Return the error-condition (if present) or the textual description of the error (otherwise). """
- errtag=self.getTag('error')
- if errtag:
- for tag in errtag.getChildren():
- if tag.getName()!='text': return tag.getName()
- return errtag.getData()
- def getErrorMsg(self):
- """ Return the textual description of the error (if present) or the error condition """
- errtag=self.getTag('error')
- if errtag:
- for tag in errtag.getChildren():
- if tag.getName()=='text': return tag.getData()
- return self.getError()
- def getErrorCode(self):
- """ Return the error code. Obsolete. """
- return self.getTagAttr('error','code')
- def setError(self,error,code=None):
- """ Set the error code. Obsolete. Use error-conditions instead. """
- if code:
- if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error)
- else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error)
- elif isinstance(error, basestring): error=ErrorNode(error)
- self.setType('error')
- self.addChild(node=error)
- def setTimestamp(self,val=None):
- """Set the timestamp. timestamp should be the yyyymmddThhmmss string."""
- if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
- self.timestamp=val
- self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
- def getProperties(self):
- """ Return the list of namespaces to which belongs the direct childs of element"""
- props=[]
- for child in self.getChildren():
- prop=child.getNamespace()
- if prop not in props: props.append(prop)
- return props
- def __setitem__(self,item,val):
- """ Set the item 'item' to the value 'val'."""
- if item in ['to','from']: val=JID(val)
- return self.setAttr(item,val)
+ ''' A "stanza" object class. Contains methods that are common for presences, iqs and messages. '''
+ def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
+ ''' Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'.
+ to is the value of 'to' attribure, 'typ' - 'type' attribute
+ frn - from attribure, attrs - other attributes mapping,
+ payload - same meaning as for simplexml payload definition
+ timestamp - the time value that needs to be stamped over stanza
+ xmlns - namespace of top stanza node
+ node - parsed or unparsed stana to be taken as prototype.
+ '''
+ if not attrs: attrs={}
+ if to: attrs['to']=to
+ if frm: attrs['from']=frm
+ if typ: attrs['type']=typ
+ Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
+ if not node and xmlns: self.setNamespace(xmlns)
+ if self['to']: self.setTo(self['to'])
+ if self['from']: self.setFrom(self['from'])
+ if node and type(self)==type(node) and self.__class__==node.__class__ and self.attrs.has_key('id'): del self.attrs['id']
+ self.timestamp=None
+ for d in self.getTags('delay',namespace=NS_DELAY2):
+ try:
+ if d.getAttr('stamp') < self.getTimestamp2():
+ self.setTimestamp(d.getAttr('stamp'))
+ except Exception:
+ pass
+ if not self.timestamp:
+ for x in self.getTags('x',namespace=NS_DELAY):
+ try:
+ if x.getAttr('stamp') < self.getTimestamp():
+ self.setTimestamp(x.getAttr('stamp'))
+ except Exception:
+ pass
+ if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
+ def getTo(self):
+ ''' Return value of the 'to' attribute. '''
+ try: return self['to']
+ except: return None
+ def getFrom(self):
+ ''' Return value of the 'from' attribute. '''
+ try: return self['from']
+ except: return None
+ def getTimestamp(self):
+ ''' Return the timestamp in the 'yyyymmddThhmmss' format. '''
+ if self.timestamp: return self.timestamp
+ return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
+ def getTimestamp2(self):
+ """ Return the timestamp in the 'yyyymmddThhmmss' format. """
+ if self.timestamp: return self.timestamp
+ return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
+ def getID(self):
+ ''' Return the value of the 'id' attribute. '''
+ return self.getAttr('id')
+ def setTo(self,val):
+ ''' Set the value of the 'to' attribute. '''
+ self.setAttr('to', JID(val))
+ def getType(self):
+ ''' Return the value of the 'type' attribute. '''
+ return self.getAttr('type')
+ def setFrom(self,val):
+ ''' Set the value of the 'from' attribute. '''
+ self.setAttr('from', JID(val))
+ def setType(self,val):
+ ''' Set the value of the 'type' attribute. '''
+ self.setAttr('type', val)
+ def setID(self,val):
+ ''' Set the value of the 'id' attribute. '''
+ self.setAttr('id', val)
+ def getError(self):
+ ''' Return the error-condition (if present) or the textual description of the error (otherwise). '''
+ errtag=self.getTag('error')
+ if errtag:
+ for tag in errtag.getChildren():
+ if tag.getName()<>'text': return tag.getName()
+ return errtag.getData()
+ def getErrorMsg(self):
+ ''' Return the textual description of the error (if present) or the error condition '''
+ errtag=self.getTag('error')
+ if errtag:
+ for tag in errtag.getChildren():
+ if tag.getName()=='text': return tag.getData()
+ return self.getError()
+ def getErrorCode(self):
+ ''' Return the error code. Obsolete. '''
+ return self.getTagAttr('error','code')
+ def setError(self,error,code=None):
+ ''' Set the error code. Obsolete. Use error-conditions instead. '''
+ if code:
+ if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error)
+ else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error)
+ elif type(error) in [type(''),type(u'')]: error=ErrorNode(error)
+ self.setType('error')
+ self.addChild(node=error)
+ def setTimestamp(self,val=None):
+ '''Set the timestamp. timestamp should be the yyyymmddThhmmss string.'''
+ if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
+ self.timestamp=val
+ self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
+ def getProperties(self):
+ ''' Return the list of namespaces to which belongs the direct childs of element'''
+ props=[]
+ for child in self.getChildren():
+ prop=child.getNamespace()
+ if prop not in props: props.append(prop)
+ return props
+ def __setitem__(self,item,val):
+ ''' Set the item 'item' to the value 'val'.'''
+ if item in ['to','from']: val=JID(val)
+ return self.setAttr(item,val)
+
class Message(Protocol):
- """ XMPP Message stanza - "push" mechanism."""
- def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
- """ Create message object. You can specify recipient, text of message, type of message
- any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
- Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """
- Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
- if body: self.setBody(body)
- if xhtml: self.setXHTML(xhtml)
- if subject is not None: self.setSubject(subject)
- def getBody(self):
- """ Returns text of the message. """
- return self.getTagData('body')
- def getXHTML(self, xmllang=None):
- """ Returns serialized xhtml-im element text of the message.
+ ''' XMPP Message stanza - "push" mechanism.'''
+ def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
+ ''' Create message object. You can specify recipient, text of message, type of message
+ any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
+ Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. '''
+ Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
+ if body: self.setBody(body)
+ if xhtml: self.setXHTML(xhtml)
+ if subject is not None: self.setSubject(subject)
+ def getBody(self):
+ ''' Returns text of the message. '''
+ return self.getTagData('body')
+ def getXHTML(self, xmllang=None):
+ ''' Returns serialized xhtml-im element text of the message.
- TODO: Returning a DOM could make rendering faster."""
- xhtml = self.getTag('html')
- if xhtml:
- if xmllang:
- body = xhtml.getTag('body', attrs={'xml:lang':xmllang})
- else:
- body = xhtml.getTag('body')
- return str(body)
- return None
- def getSubject(self):
- """ Returns subject of the message. """
- return self.getTagData('subject')
- def getThread(self):
- """ Returns thread of the message. """
- return self.getTagData('thread')
- def setBody(self,val):
- """ Sets the text of the message. """
- self.setTagData('body',val)
+ TODO: Returning a DOM could make rendering faster.'''
+ xhtml = self.getTag('html')
+ if xhtml:
+ if xmllang:
+ body = xhtml.getTag('body', attrs={'xml:lang':xmllang})
+ else:
+ body = xhtml.getTag('body')
+ return str(body)
+ return None
+ def getSubject(self):
+ ''' Returns subject of the message. '''
+ return self.getTagData('subject')
+ def getThread(self):
+ ''' Returns thread of the message. '''
+ return self.getTagData('thread')
+ def setBody(self,val):
+ ''' Sets the text of the message. '''
+ self.setTagData('body',val)
- def setXHTML(self,val,xmllang=None):
- """ Sets the xhtml text of the message (XEP-0071).
- The parameter is the "inner html" to the body."""
- try:
- if xmllang:
- dom = NodeBuilder('' + val + '').getDom()
- else:
- dom = NodeBuilder(''+val+'',0).getDom()
- if self.getTag('html'):
- self.getTag('html').addChild(node=dom)
- else:
- self.setTag('html',namespace=NS_XHTML_IM).addChild(node=dom)
- except Exception, e:
- print "Error", e
- #FIXME: log. we could not set xhtml (parse error, whatever)
- def setSubject(self,val):
- """ Sets the subject of the message. """
- self.setTagData('subject',val)
- def setThread(self,val):
- """ Sets the thread of the message. """
- self.setTagData('thread',val)
- def buildReply(self,text=None):
- """ Builds and returns another message object with specified text.
- The to, from and thread properties of new message are pre-set as reply to this message. """
- m=Message(to=self.getFrom(),frm=self.getTo(),body=text,node=self)
- th=self.getThread()
- if th: m.setThread(th)
- return m
- def getStatusCode(self):
- """Returns the status code of the message (for groupchat config
- change)"""
- attrs = []
- for xtag in self.getTags('x'):
- for child in xtag.getTags('status'):
- attrs.append(child.getAttr('code'))
- return attrs
+ def setXHTML(self,val,xmllang=None):
+ ''' Sets the xhtml text of the message (XEP-0071).
+ The parameter is the "inner html" to the body.'''
+ try:
+ if xmllang:
+ dom = NodeBuilder('' + val + '').getDom()
+ else:
+ dom = NodeBuilder(''+val+'',0).getDom()
+ if self.getTag('html'):
+ self.getTag('html').addChild(node=dom)
+ else:
+ self.setTag('html',namespace=NS_XHTML_IM).addChild(node=dom)
+ except Exception, e:
+ print "Error", e
+ #FIXME: log. we could not set xhtml (parse error, whatever)
+ def setSubject(self,val):
+ ''' Sets the subject of the message. '''
+ self.setTagData('subject',val)
+ def setThread(self,val):
+ ''' Sets the thread of the message. '''
+ self.setTagData('thread',val)
+ def buildReply(self,text=None):
+ ''' Builds and returns another message object with specified text.
+ The to, from and thread properties of new message are pre-set as reply to this message. '''
+ m=Message(to=self.getFrom(),frm=self.getTo(),body=text,node=self)
+ th=self.getThread()
+ if th: m.setThread(th)
+ return m
+ def getStatusCode(self):
+ '''Returns the status code of the message (for groupchat config
+ change)'''
+ attrs = []
+ for xtag in self.getTags('x'):
+ for child in xtag.getTags('status'):
+ attrs.append(child.getAttr('code'))
+ return attrs
class Presence(Protocol):
- """ XMPP Presence object."""
- def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
- """ Create presence object. You can specify recipient, type of message, priority, show and status values
- any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
- Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. """
- Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
- if priority: self.setPriority(priority)
- if show: self.setShow(show)
- if status: self.setStatus(status)
- def getPriority(self):
- """ Returns the priority of the message. """
- return self.getTagData('priority')
- def getShow(self):
- """ Returns the show value of the message. """
- return self.getTagData('show')
- def getStatus(self):
- """ Returns the status string of the message. """
- return self.getTagData('status')
- def setPriority(self,val):
- """ Sets the priority of the message. """
- self.setTagData('priority',val)
- def setShow(self,val):
- """ Sets the show value of the message. """
- self.setTagData('show',val)
- def setStatus(self,val):
- """ Sets the status string of the message. """
- self.setTagData('status',val)
+ ''' XMPP Presence object.'''
+ def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
+ ''' Create presence object. You can specify recipient, type of message, priority, show and status values
+ any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
+ Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. '''
+ Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
+ if priority: self.setPriority(priority)
+ if show: self.setShow(show)
+ if status: self.setStatus(status)
+ def getPriority(self):
+ ''' Returns the priority of the message. '''
+ return self.getTagData('priority')
+ def getShow(self):
+ ''' Returns the show value of the message. '''
+ return self.getTagData('show')
+ def getStatus(self):
+ ''' Returns the status string of the message. '''
+ return self.getTagData('status')
+ def setPriority(self,val):
+ ''' Sets the priority of the message. '''
+ self.setTagData('priority',val)
+ def setShow(self,val):
+ ''' Sets the show value of the message. '''
+ self.setTagData('show',val)
+ def setStatus(self,val):
+ ''' Sets the status string of the message. '''
+ self.setTagData('status',val)
- def _muc_getItemAttr(self,tag,attr):
- for xtag in self.getTags('x'):
- if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN):
- continue
- for child in xtag.getTags(tag):
- return child.getAttr(attr)
- def _muc_getSubTagDataAttr(self,tag,attr):
- for xtag in self.getTags('x'):
- if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN):
- continue
- for child in xtag.getTags('item'):
- for cchild in child.getTags(tag):
- return cchild.getData(),cchild.getAttr(attr)
- return None,None
- def getRole(self):
- """Returns the presence role (for groupchat)"""
- return self._muc_getItemAttr('item','role')
- def getAffiliation(self):
- """Returns the presence affiliation (for groupchat)"""
- return self._muc_getItemAttr('item','affiliation')
- def getNewNick(self):
- """Returns the status code of the presence (for groupchat)"""
- return self._muc_getItemAttr('item','nick')
- def getJid(self):
- """Returns the presence jid (for groupchat)"""
- return self._muc_getItemAttr('item','jid')
- def getReason(self):
- """Returns the reason of the presence (for groupchat)"""
- return self._muc_getSubTagDataAttr('reason','')[0]
- def getActor(self):
- """Returns the reason of the presence (for groupchat)"""
- return self._muc_getSubTagDataAttr('actor','jid')[1]
- def getStatusCode(self):
- """Returns the status code of the presence (for groupchat)"""
- attrs = []
- for xtag in self.getTags('x'):
- for child in xtag.getTags('status'):
- attrs.append(child.getAttr('code'))
- return attrs
+ def _muc_getItemAttr(self,tag,attr):
+ for xtag in self.getTags('x'):
+ if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN):
+ continue
+ for child in xtag.getTags(tag):
+ return child.getAttr(attr)
+ def _muc_getSubTagDataAttr(self,tag,attr):
+ for xtag in self.getTags('x'):
+ if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN):
+ continue
+ for child in xtag.getTags('item'):
+ for cchild in child.getTags(tag):
+ return cchild.getData(),cchild.getAttr(attr)
+ return None,None
+ def getRole(self):
+ '''Returns the presence role (for groupchat)'''
+ return self._muc_getItemAttr('item','role')
+ def getAffiliation(self):
+ '''Returns the presence affiliation (for groupchat)'''
+ return self._muc_getItemAttr('item','affiliation')
+ def getNewNick(self):
+ '''Returns the status code of the presence (for groupchat)'''
+ return self._muc_getItemAttr('item','nick')
+ def getJid(self):
+ '''Returns the presence jid (for groupchat)'''
+ return self._muc_getItemAttr('item','jid')
+ def getReason(self):
+ '''Returns the reason of the presence (for groupchat)'''
+ return self._muc_getSubTagDataAttr('reason','')[0]
+ def getActor(self):
+ '''Returns the reason of the presence (for groupchat)'''
+ return self._muc_getSubTagDataAttr('actor','jid')[1]
+ def getStatusCode(self):
+ '''Returns the status code of the presence (for groupchat)'''
+ attrs = []
+ for xtag in self.getTags('x'):
+ for child in xtag.getTags('status'):
+ attrs.append(child.getAttr('code'))
+ return attrs
class Iq(Protocol):
- """ XMPP Iq object - get/set dialog mechanism. """
- def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
- """ Create Iq object. You can specify type, query namespace
- any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.
- Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. """
- Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
- if payload: self.setQueryPayload(payload)
- if queryNS: self.setQueryNS(queryNS)
- def getQueryNS(self):
- """ Return the namespace of the 'query' child element."""
- tag=self.getTag('query')
- if tag: return tag.getNamespace()
- def getQuerynode(self):
- """ Return the 'node' attribute value of the 'query' child element."""
- return self.getTagAttr('query','node')
- def getQueryPayload(self):
- """ Return the 'query' child element payload."""
- tag=self.getTag('query')
- if tag: return tag.getPayload()
- def getQueryChildren(self):
- """ Return the 'query' child element child nodes."""
- tag=self.getTag('query')
- if tag: return tag.getChildren()
- def setQueryNS(self,namespace):
- """ Set the namespace of the 'query' child element."""
- self.setTag('query').setNamespace(namespace)
- def setQueryPayload(self,payload):
- """ Set the 'query' child element payload."""
- self.setTag('query').setPayload(payload)
- def setQuerynode(self,node):
- """ Set the 'node' attribute value of the 'query' child element."""
- self.setTagAttr('query','node',node)
- def buildReply(self,typ):
- """ Builds and returns another Iq object of specified type.
- The to, from and query child node of new Iq are pre-set as reply to this Iq. """
- iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()})
- if self.getTag('query'): iq.setQueryNS(self.getQueryNS())
- return iq
+ ''' XMPP Iq object - get/set dialog mechanism. '''
+ def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
+ ''' Create Iq object. You can specify type, query namespace
+ any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.
+ Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. '''
+ Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
+ if payload: self.setQueryPayload(payload)
+ if queryNS: self.setQueryNS(queryNS)
+ def getQueryNS(self):
+ ''' Return the namespace of the 'query' child element.'''
+ tag=self.getTag('query')
+ if tag: return tag.getNamespace()
+ def getQuerynode(self):
+ ''' Return the 'node' attribute value of the 'query' child element.'''
+ return self.getTagAttr('query','node')
+ def getQueryPayload(self):
+ ''' Return the 'query' child element payload.'''
+ tag=self.getTag('query')
+ if tag: return tag.getPayload()
+ def getQueryChildren(self):
+ ''' Return the 'query' child element child nodes.'''
+ tag=self.getTag('query')
+ if tag: return tag.getChildren()
+ def setQueryNS(self,namespace):
+ ''' Set the namespace of the 'query' child element.'''
+ self.setTag('query').setNamespace(namespace)
+ def setQueryPayload(self,payload):
+ ''' Set the 'query' child element payload.'''
+ self.setTag('query').setPayload(payload)
+ def setQuerynode(self,node):
+ ''' Set the 'node' attribute value of the 'query' child element.'''
+ self.setTagAttr('query','node',node)
+ def buildReply(self,typ):
+ ''' Builds and returns another Iq object of specified type.
+ The to, from and query child node of new Iq are pre-set as reply to this Iq. '''
+ iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()})
+ if self.getTag('query'): iq.setQueryNS(self.getQueryNS())
+ return iq
class ErrorNode(Node):
- """ XMPP-style error element.
- In the case of stanza error should be attached to XMPP stanza.
- In the case of stream-level errors should be used separately. """
- def __init__(self,name,code=None,typ=None,text=None):
- """ Create new error node object.
- Mandatory parameter: name - name of error condition.
- Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol."""
- if name in ERRORS:
- cod,type_,txt=ERRORS[name]
- ns=name.split()[0]
- else: cod,ns,type_,txt='500',NS_STANZAS,'cancel',''
- if typ: type_=typ
- if code: cod=code
- if text: txt=text
- Node.__init__(self,'error',{},[Node(name)])
- if type_: self.setAttr('type',type_)
- if not cod: self.setName('stream:error')
- if txt: self.addChild(node=Node(ns+' text',{},[txt]))
- if cod: self.setAttr('code',cod)
+ ''' XMPP-style error element.
+ In the case of stanza error should be attached to XMPP stanza.
+ In the case of stream-level errors should be used separately. '''
+ def __init__(self,name,code=None,typ=None,text=None):
+ ''' Create new error node object.
+ Mandatory parameter: name - name of error condition.
+ Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.'''
+ if name in ERRORS:
+ cod,type_,txt=ERRORS[name]
+ ns=name.split()[0]
+ else: cod,ns,type_,txt='500',NS_STANZAS,'cancel',''
+ if typ: type_=typ
+ if code: cod=code
+ if text: txt=text
+ Node.__init__(self,'error',{},[Node(name)])
+ if type_: self.setAttr('type',type_)
+ if not cod: self.setName('stream:error')
+ if txt: self.addChild(node=Node(ns+' text',{},[txt]))
+ if cod: self.setAttr('code',cod)
class Error(Protocol):
- """ Used to quickly transform received stanza into error reply."""
- def __init__(self,node,error,reply=1):
- """ Create error reply basing on the received 'node' stanza and the 'error' error condition.
- If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping)
- specify the 'reply' argument as false."""
- if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node)
- else: Protocol.__init__(self,node=node)
- self.setError(error)
- if node.getType()=='error': self.__str__=self.__dupstr__
- def __dupstr__(self,dup1=None,dup2=None):
- """ Dummy function used as preventor of creating error node in reply to error node.
- I.e. you will not be able to serialise "double" error into string.
- """
- return ''
+ ''' Used to quickly transform received stanza into error reply.'''
+ def __init__(self,node,error,reply=1):
+ ''' Create error reply basing on the received 'node' stanza and the 'error' error condition.
+ If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping)
+ specify the 'reply' argument as false.'''
+ if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node)
+ else: Protocol.__init__(self,node=node)
+ self.setError(error)
+ if node.getType()=='error': self.__str__=self.__dupstr__
+ def __dupstr__(self,dup1=None,dup2=None):
+ ''' Dummy function used as preventor of creating error node in reply to error node.
+ I.e. you will not be able to serialise "double" error into string.
+ '''
+ return ''
class DataField(Node):
- """ This class is used in the DataForm class to describe the single data item.
- If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122)
- then you will need to work with instances of this class. """
- def __init__(self,name=None,value=None,typ=None,required=0,desc=None,options=[],node=None):
- """ Create new data field of specified name,value and type.
- Also 'required','desc' and 'options' fields can be set.
- Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled.
- """
- Node.__init__(self,'field',node=node)
- if name: self.setVar(name)
- if isinstance(value, (list, tuple)): self.setValues(value)
- elif value: self.setValue(value)
- if typ: self.setType(typ)
- elif not typ and not node: self.setType('text-single')
- if required: self.setRequired(required)
- if desc: self.setDesc(desc)
- if options: self.setOptions(options)
- def setRequired(self,req=1):
- """ Change the state of the 'required' flag. """
- if req: self.setTag('required')
- else:
- try: self.delChild('required')
- except ValueError: return
- def isRequired(self):
- """ Returns in this field a required one. """
- return self.getTag('required')
- def setDesc(self,desc):
- """ Set the description of this field. """
- self.setTagData('desc',desc)
- def getDesc(self):
- """ Return the description of this field. """
- return self.getTagData('desc')
- def setValue(self,val):
- """ Set the value of this field. """
- self.setTagData('value',val)
- def getValue(self):
- return self.getTagData('value')
- def setValues(self,lst):
- """ Set the values of this field as values-list.
- Replaces all previous filed values! If you need to just add a value - use addValue method."""
- while self.getTag('value'): self.delChild('value')
- for val in lst: self.addValue(val)
- def addValue(self,val):
- """ Add one more value to this field. Used in 'get' iq's or such."""
- self.addChild('value',{},[val])
- def getValues(self):
- """ Return the list of values associated with this field."""
- ret=[]
- for tag in self.getTags('value'): ret.append(tag.getData())
- return ret
- def getOptions(self):
- """ Return label-option pairs list associated with this field."""
- ret=[]
- for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')])
- return ret
- def setOptions(self,lst):
- """ Set label-option pairs list associated with this field."""
- while self.getTag('option'): self.delChild('option')
- for opt in lst: self.addOption(opt)
- def addOption(self,opt):
- """ Add one more label-option pair to this field."""
- if isinstance(opt, basestring): self.addChild('option').setTagData('value',opt)
- else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
- def getType(self):
- """ Get type of this field. """
- return self.getAttr('type')
- def setType(self,val):
- """ Set type of this field. """
- return self.setAttr('type',val)
- def getVar(self):
- """ Get 'var' attribute value of this field. """
- return self.getAttr('var')
- def setVar(self,val):
- """ Set 'var' attribute value of this field. """
- return self.setAttr('var',val)
+ ''' This class is used in the DataForm class to describe the single data item.
+ If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122)
+ then you will need to work with instances of this class. '''
+ def __init__(self,name=None,value=None,typ=None,required=0,desc=None,options=[],node=None):
+ ''' Create new data field of specified name,value and type.
+ Also 'required','desc' and 'options' fields can be set.
+ Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled.
+ '''
+ Node.__init__(self,'field',node=node)
+ if name: self.setVar(name)
+ if isinstance(value, (list, tuple)): self.setValues(value)
+ elif value: self.setValue(value)
+ if typ: self.setType(typ)
+ elif not typ and not node: self.setType('text-single')
+ if required: self.setRequired(required)
+ if desc: self.setDesc(desc)
+ if options: self.setOptions(options)
+ def setRequired(self,req=1):
+ ''' Change the state of the 'required' flag. '''
+ if req: self.setTag('required')
+ else:
+ try: self.delChild('required')
+ except ValueError: return
+ def isRequired(self):
+ ''' Returns in this field a required one. '''
+ return self.getTag('required')
+ def setDesc(self,desc):
+ ''' Set the description of this field. '''
+ self.setTagData('desc',desc)
+ def getDesc(self):
+ ''' Return the description of this field. '''
+ return self.getTagData('desc')
+ def setValue(self,val):
+ ''' Set the value of this field. '''
+ self.setTagData('value',val)
+ def getValue(self):
+ return self.getTagData('value')
+ def setValues(self,lst):
+ ''' Set the values of this field as values-list.
+ Replaces all previous filed values! If you need to just add a value - use addValue method.'''
+ while self.getTag('value'): self.delChild('value')
+ for val in lst: self.addValue(val)
+ def addValue(self,val):
+ ''' Add one more value to this field. Used in 'get' iq's or such.'''
+ self.addChild('value',{},[val])
+ def getValues(self):
+ ''' Return the list of values associated with this field.'''
+ ret=[]
+ for tag in self.getTags('value'): ret.append(tag.getData())
+ return ret
+ def getOptions(self):
+ ''' Return label-option pairs list associated with this field.'''
+ ret=[]
+ for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')])
+ return ret
+ def setOptions(self,lst):
+ ''' Set label-option pairs list associated with this field.'''
+ while self.getTag('option'): self.delChild('option')
+ for opt in lst: self.addOption(opt)
+ def addOption(self,opt):
+ ''' Add one more label-option pair to this field.'''
+ if isinstance(opt, basestring): self.addChild('option').setTagData('value',opt)
+ else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
+ def getType(self):
+ ''' Get type of this field. '''
+ return self.getAttr('type')
+ def setType(self,val):
+ ''' Set type of this field. '''
+ return self.setAttr('type',val)
+ def getVar(self):
+ ''' Get 'var' attribute value of this field. '''
+ return self.getAttr('var')
+ def setVar(self,val):
+ ''' Set 'var' attribute value of this field. '''
+ return self.setAttr('var',val)
class DataForm(Node):
- """ DataForm class. Used for manipulating dataforms in XMPP.
- Relevant XEPs: 0004, 0068, 0122.
- Can be used in disco, pub-sub and many other applications."""
- def __init__(self, typ=None, data=[], title=None, node=None):
- """
- Create new dataform of type 'typ'. 'data' is the list of DataField
- instances that this dataform contains, 'title' - the title string.
- You can specify the 'node' argument as the other node to be used as
- base for constructing this dataform.
+ ''' DataForm class. Used for manipulating dataforms in XMPP.
+ Relevant XEPs: 0004, 0068, 0122.
+ Can be used in disco, pub-sub and many other applications.'''
+ def __init__(self, typ=None, data=[], title=None, node=None):
+ '''
+ Create new dataform of type 'typ'. 'data' is the list of DataField
+ instances that this dataform contains, 'title' - the title string.
+ You can specify the 'node' argument as the other node to be used as
+ base for constructing this dataform.
- title and instructions is optional and SHOULD NOT contain newlines.
- Several instructions MAY be present.
- 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' )
- 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively.
- 'cancel' form can not contain any fields. All other forms contains AT LEAST one field.
- 'title' MAY be included in forms of type "form" and "result"
- """
- Node.__init__(self,'x',node=node)
- if node:
- newkids=[]
- for n in self.getChildren():
- if n.getName()=='field': newkids.append(DataField(node=n))
- else: newkids.append(n)
- self.kids=newkids
- if typ: self.setType(typ)
- self.setNamespace(NS_DATA)
- if title: self.setTitle(title)
- if isinstance(data, dict):
- newdata=[]
- for name in data.keys(): newdata.append(DataField(name,data[name]))
- data=newdata
- for child in data:
- if isinstance(child, basestring): self.addInstructions(child)
- elif child.__class__.__name__=='DataField': self.kids.append(child)
- else: self.kids.append(DataField(node=child))
- def getType(self):
- """ Return the type of dataform. """
- return self.getAttr('type')
- def setType(self,typ):
- """ Set the type of dataform. """
- self.setAttr('type',typ)
- def getTitle(self):
- """ Return the title of dataform. """
- return self.getTagData('title')
- def setTitle(self,text):
- """ Set the title of dataform. """
- self.setTagData('title',text)
- def getInstructions(self):
- """ Return the instructions of dataform. """
- return self.getTagData('instructions')
- def setInstructions(self,text):
- """ Set the instructions of dataform. """
- self.setTagData('instructions',text)
- def addInstructions(self,text):
- """ Add one more instruction to the dataform. """
- self.addChild('instructions',{},[text])
- def getField(self,name):
- """ Return the datafield object with name 'name' (if exists). """
- return self.getTag('field',attrs={'var':name})
- def setField(self,name):
- """ Create if nessessary or get the existing datafield object with name 'name' and return it. """
- f=self.getField(name)
- if f: return f
- return self.addChild(node=DataField(name))
- def asDict(self):
- """ Represent dataform as simple dictionary mapping of datafield names to their values."""
- ret={}
- for field in self.getTags('field'):
- name=field.getAttr('var')
- typ=field.getType()
- if isinstance(typ, basestring) and typ.endswith('-multi'):
- val=[]
- for i in field.getTags('value'): val.append(i.getData())
- else: val=field.getTagData('value')
- ret[name]=val
- if self.getTag('instructions'): ret['instructions']=self.getInstructions()
- return ret
- def __getitem__(self,name):
- """ Simple dictionary interface for getting datafields values by their names."""
- item=self.getField(name)
- if item: return item.getValue()
- raise IndexError('No such field')
- def __setitem__(self,name,val):
- """ Simple dictionary interface for setting datafields values by their names."""
- return self.setField(name).setValue(val)
+ title and instructions is optional and SHOULD NOT contain newlines.
+ Several instructions MAY be present.
+ 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' )
+ 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively.
+ 'cancel' form can not contain any fields. All other forms contains AT LEAST one field.
+ 'title' MAY be included in forms of type "form" and "result"
+ '''
+ Node.__init__(self,'x',node=node)
+ if node:
+ newkids=[]
+ for n in self.getChildren():
+ if n.getName()=='field': newkids.append(DataField(node=n))
+ else: newkids.append(n)
+ self.kids=newkids
+ if typ: self.setType(typ)
+ self.setNamespace(NS_DATA)
+ if title: self.setTitle(title)
+ if isinstance(data, dict):
+ newdata=[]
+ for name in data.keys(): newdata.append(DataField(name,data[name]))
+ data=newdata
+ for child in data:
+ if isinstance(child, basestring): self.addInstructions(child)
+ elif child.__class__.__name__=='DataField': self.kids.append(child)
+ else: self.kids.append(DataField(node=child))
+ def getType(self):
+ ''' Return the type of dataform. '''
+ return self.getAttr('type')
+ def setType(self,typ):
+ ''' Set the type of dataform. '''
+ self.setAttr('type',typ)
+ def getTitle(self):
+ ''' Return the title of dataform. '''
+ return self.getTagData('title')
+ def setTitle(self,text):
+ ''' Set the title of dataform. '''
+ self.setTagData('title',text)
+ def getInstructions(self):
+ ''' Return the instructions of dataform. '''
+ return self.getTagData('instructions')
+ def setInstructions(self,text):
+ ''' Set the instructions of dataform. '''
+ self.setTagData('instructions',text)
+ def addInstructions(self,text):
+ ''' Add one more instruction to the dataform. '''
+ self.addChild('instructions',{},[text])
+ def getField(self,name):
+ ''' Return the datafield object with name 'name' (if exists). '''
+ return self.getTag('field',attrs={'var':name})
+ def setField(self,name):
+ ''' Create if nessessary or get the existing datafield object with name 'name' and return it. '''
+ f=self.getField(name)
+ if f: return f
+ return self.addChild(node=DataField(name))
+ def asDict(self):
+ ''' Represent dataform as simple dictionary mapping of datafield names to their values.'''
+ ret={}
+ for field in self.getTags('field'):
+ name=field.getAttr('var')
+ typ=field.getType()
+ if isinstance(typ, basestring) and typ.endswith('-multi'):
+ val=[]
+ for i in field.getTags('value'): val.append(i.getData())
+ else: val=field.getTagData('value')
+ ret[name]=val
+ if self.getTag('instructions'): ret['instructions']=self.getInstructions()
+ return ret
+ def __getitem__(self,name):
+ ''' Simple dictionary interface for getting datafields values by their names.'''
+ item=self.getField(name)
+ if item: return item.getValue()
+ raise IndexError('No such field')
+ def __setitem__(self,name,val):
+ ''' Simple dictionary interface for setting datafields values by their names.'''
+ return self.setField(name).setValue(val)
# vim: se ts=3:
diff --git a/src/common/xmpp/proxy_connectors.py b/src/common/xmpp/proxy_connectors.py
new file mode 100644
index 000000000..1cb30d3b1
--- /dev/null
+++ b/src/common/xmpp/proxy_connectors.py
@@ -0,0 +1,222 @@
+## proxy_connectors.py
+## based on transports_nb.py
+##
+## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
+## modified by Dimitur Kirov
+## modified by Tomas Karasek
+##
+## 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; either version 2, or (at your option)
+## any later version.
+##
+## 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 struct, socket, base64
+
+'''
+Module containing classes for proxy connecting. So far its HTTP CONNECT
+and SOCKS5 proxy.
+Authentication to NTLM (Microsoft implementation) proxies can be next.
+'''
+
+import logging
+log = logging.getLogger('gajim.c.x.proxy_connectors')
+
+class ProxyConnector:
+ '''
+ Interface for proxy-connecting object - when tunnneling XMPP over proxies,
+ some connecting process usually has to be done before opening stream.
+ Proxy connectors are used right after TCP connection is estabilished.
+ '''
+ def __init__(self, send_method, onreceive, old_on_receive, on_success,
+ on_failure, xmpp_server, proxy_creds=(None,None)):
+
+ self.send = send_method
+ self.onreceive = onreceive
+ self.old_on_receive = old_on_receive
+ self.on_success = on_success
+ self.on_failure = on_failure
+ self.xmpp_server = xmpp_server
+ self.proxy_user, self.proxy_pass = proxy_creds
+ self.old_on_receive = old_on_receive
+
+ self.start_connecting()
+
+ def start_connecting(self):
+ raise NotImplementedException()
+
+ def connecting_over(self):
+ self.onreceive(self.old_on_receive)
+ self.on_success()
+
+class HTTPCONNECTConnector(ProxyConnector):
+ def start_connecting(self):
+ '''
+ Connects to proxy, supplies login and password to it
+ (if were specified while creating instance). Instructs proxy to make
+ connection to the target server.
+ '''
+ log.info('Proxy server contacted, performing authentification')
+ connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server,
+ 'Proxy-Connection: Keep-Alive',
+ 'Pragma: no-cache',
+ 'Host: %s:%s' % self.xmpp_server,
+ 'User-Agent: Gajim']
+ if self.proxy_user and self.proxy_pass:
+ credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
+ credentials = base64.encodestring(credentials).strip()
+ connector.append('Proxy-Authorization: Basic '+credentials)
+ connector.append('\r\n')
+ self.onreceive(self._on_headers_sent)
+ self.send('\r\n'.join(connector))
+
+ def _on_headers_sent(self, reply):
+ if reply is None:
+ return
+ self.reply = reply.replace('\r', '')
+ try:
+ proto, code, desc = reply.split('\n')[0].split(' ', 2)
+ except:
+ log.error("_on_headers_sent:", exc_info=True)
+ #traceback.print_exc()
+ self.on_failure('Invalid proxy reply')
+ return
+ if code <> '200':
+ log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc))
+ self.on_failure('Invalid proxy reply')
+ return
+ if len(reply) != 2:
+ pass
+ self.connecting_over()
+
+
+
+class SOCKS5Connector(ProxyConnector):
+ '''
+ SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with
+ (optionally) simple authentication (only USERNAME/PASSWORD auth).
+ '''
+ def start_connecting(self):
+ log.info('Proxy server contacted, performing authentification')
+ if self.proxy_user and self.proxy_pass:
+ to_send = '\x05\x02\x00\x02'
+ else:
+ to_send = '\x05\x01\x00'
+ self.onreceive(self._on_greeting_sent)
+ self.send(to_send)
+
+ def _on_greeting_sent(self, reply):
+ if reply is None:
+ return
+ if len(reply) != 2:
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[0] != '\x05':
+ log.info('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[1] == '\x00':
+ return self._on_proxy_auth('\x01\x00')
+ elif reply[1] == '\x02':
+ to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\
+ chr(len(self.proxy_pass)) + self.proxy_pass
+ self.onreceive(self._on_proxy_auth)
+ self.send(to_send)
+ else:
+ if reply[1] == '\xff':
+ log.error('Authentification to proxy impossible: no acceptable '
+ 'auth method')
+ self.on_failure('Authentification to proxy impossible: no '
+ 'acceptable authentification method')
+ return
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+
+ def _on_proxy_auth(self, reply):
+ if reply is None:
+ return
+ if len(reply) != 2:
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[0] != '\x01':
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[1] != '\x00':
+ log.error('Authentification to proxy failed')
+ self.on_failure('Authentification to proxy failed')
+ return
+ log.info('Authentification successfull. Jabber server contacted.')
+ # Request connection
+ req = "\x05\x01\x00"
+ # If the given destination address is an IP address, we'll
+ # use the IPv4 address request even if remote resolving was specified.
+ try:
+ self.ipaddr = socket.inet_aton(self.xmpp_server[0])
+ req = req + "\x01" + self.ipaddr
+ except socket.error:
+ # Well it's not an IP number, so it's probably a DNS name.
+# if self.__proxy[3]==True:
+ # Resolve remotely
+ self.ipaddr = None
+ req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0]
+# else:
+# # Resolve locally
+# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0]))
+# req = req + "\x01" + ipaddr
+ req = req + struct.pack(">H",self.xmpp_server[1])
+ self.onreceive(self._on_req_sent)
+ self.send(req)
+
+ def _on_req_sent(self, reply):
+ if reply is None:
+ return
+ if len(reply) < 10:
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[0] != '\x05':
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[1] != "\x00":
+ # Connection failed
+ if ord(reply[1])<9:
+ errors = ['general SOCKS server failure',
+ 'connection not allowed by ruleset',
+ 'Network unreachable',
+ 'Host unreachable',
+ 'Connection refused',
+ 'TTL expired',
+ 'Command not supported',
+ 'Address type not supported'
+ ]
+ txt = errors[ord(reply[1])-1]
+ else:
+ txt = 'Invalid proxy reply'
+ log.error(txt)
+ self.on_failure(txt)
+ return
+ # Get the bound address/port
+ elif reply[3] == "\x01":
+ begin, end = 3, 7
+ elif reply[3] == "\x03":
+ begin, end = 4, 4 + reply[4]
+ else:
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ self.connecting_over()
+
+
+
+
+
+
+
+
diff --git a/src/common/xmpp/roster.py b/src/common/xmpp/roster.py
deleted file mode 100644
index 292b3ea8b..000000000
--- a/src/common/xmpp/roster.py
+++ /dev/null
@@ -1,199 +0,0 @@
-## roster.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-# $Id: roster.py,v 1.17 2005/05/02 08:38:49 snakeru Exp $
-
-"""
-Simple roster implementation. Can be used though for different tasks like
-mass-renaming of contacts.
-"""
-
-from protocol import *
-from client import PlugIn
-
-class Roster(PlugIn):
- """ Defines a plenty of methods that will allow you to manage roster.
- Also automatically track presences from remote JIDs taking into
- account that every JID can have multiple resources connected. Does not
- currently support 'error' presences.
- You can also use mapping interface for access to the internal representation of
- contacts in roster.
- """
- def __init__(self):
- """ Init internal variables. """
- PlugIn.__init__(self)
- self.DBG_LINE='roster'
- self._data = {}
- self.set=None
- self._exported_methods=[self.getRoster]
-
- def plugin(self,owner,request=1):
- """ Register presence and subscription trackers in the owner's dispatcher.
- Also request roster from server if the 'request' argument is set.
- Used internally."""
- self._owner.RegisterHandler('iq',self.RosterIqHandler,'result',NS_ROSTER,makefirst=1)
- self._owner.RegisterHandler('iq',self.RosterIqHandler,'set',NS_ROSTER)
- self._owner.RegisterHandler('presence',self.PresenceHandler)
- if request: self.Request()
-
- def Request(self,force=0):
- """ Request roster from server if it were not yet requested
- (or if the 'force' argument is set). """
- if self.set is None: self.set=0
- elif not force: return
- self._owner.send(Iq('get',NS_ROSTER))
- self.DEBUG('Roster requested from server','start')
-
- def getRoster(self):
- """ Requests roster from server if neccessary and returns self."""
- if not self.set: self.Request()
- while not self.set: self._owner.Process(10)
- return self
-
- def RosterIqHandler(self,dis,stanza):
- """ Subscription tracker. Used internally for setting items state in
- internal roster representation. """
- sender = stanza.getAttr('from')
- if not sender is None and not sender.bareMatch(
- self._owner.User + '@' + self._owner.Server):
- return
- query = stanza.getTag('query')
- if query:
- for item in query.getTags('item'):
- jid=item.getAttr('jid')
- if item.getAttr('subscription')=='remove':
- if jid in self._data: del self._data[jid]
- return
- self.DEBUG('Setting roster item %s...'%jid,'ok')
- if jid not in self._data: self._data[jid]={}
- self._data[jid]['name']=item.getAttr('name')
- self._data[jid]['ask']=item.getAttr('ask')
- self._data[jid]['subscription']=item.getAttr('subscription')
- self._data[jid]['groups']=[]
- if 'resources' not in self._data[jid]: self._data[jid]['resources']={}
- for group in item.getTags('group'): self._data[jid]['groups'].append(group.getData())
- self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
- self.set=1
-
- def PresenceHandler(self,dis,pres):
- """ Presence tracker. Used internally for setting items' resources state in
- internal roster representation. """
- jid=pres.getFrom()
- if not jid:
- # If no from attribue, it's from server
- jid=self._owner.Server
- jid=JID(jid)
- if jid.getStripped() not in self._data: self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
- if not isinstance(self._data[jid.getStripped()]['resources'], dict):
- self._data[jid.getStripped()]['resources']={}
- item=self._data[jid.getStripped()]
- typ=pres.getType()
-
- if not typ:
- self.DEBUG('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource()),'ok')
- item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
- if pres.getTag('show'): res['show']=pres.getShow()
- if pres.getTag('status'): res['status']=pres.getStatus()
- if pres.getTag('priority'): res['priority']=pres.getPriority()
- if not pres.getTimestamp(): pres.setTimestamp()
- res['timestamp']=pres.getTimestamp()
- elif typ=='unavailable' and jid.getResource() in item['resources']: del item['resources'][jid.getResource()]
- # Need to handle type='error' also
-
- def _getItemData(self,jid,dataname):
- """ Return specific jid's representation in internal format. Used internally. """
- jid=jid[:(jid+'/').find('/')]
- return self._data[jid][dataname]
- def _getResourceData(self,jid,dataname):
- """ Return specific jid's resource representation in internal format. Used internally. """
- if jid.find('/')+1:
- jid,resource=jid.split('/',1)
- if resource in self._data[jid]['resources']: return self._data[jid]['resources'][resource][dataname]
- elif self._data[jid]['resources'].keys():
- lastpri=-129
- for r in self._data[jid]['resources'].keys():
- if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])
- return self._data[jid]['resources'][resource][dataname]
- def delItem(self,jid):
- """ Delete contact 'jid' from roster."""
- self._owner.send(Iq('set',NS_ROSTER,payload=[Node('item',{'jid':jid,'subscription':'remove'})]))
- def getAsk(self,jid):
- """ Returns 'ask' value of contact 'jid'."""
- return self._getItemData(jid,'ask')
- def getGroups(self,jid):
- """ Returns groups list that contact 'jid' belongs to."""
- return self._getItemData(jid,'groups')
- def getName(self,jid):
- """ Returns name of contact 'jid'."""
- return self._getItemData(jid,'name')
- def getPriority(self,jid):
- """ Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID."""
- return self._getResourceData(jid,'priority')
- def getRawRoster(self):
- """ Returns roster representation in internal format. """
- return self._data
- def getRawItem(self,jid):
- """ Returns roster item 'jid' representation in internal format. """
- return self._data[jid[:(jid+'/').find('/')]]
- def getShow(self, jid):
- """ Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID."""
- return self._getResourceData(jid,'show')
- def getStatus(self, jid):
- """ Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID."""
- return self._getResourceData(jid,'status')
- def getSubscription(self,jid):
- """ Returns 'subscription' value of contact 'jid'."""
- return self._getItemData(jid,'subscription')
- def getResources(self,jid):
- """ Returns list of connected resources of contact 'jid'."""
- return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
- def setItem(self,jid,name=None,groups=[]):
- """ Renames contact 'jid' and sets the groups list that it now belongs to."""
- iq=Iq('set',NS_ROSTER)
- query=iq.getTag('query')
- attrs={'jid':jid}
- if name: attrs['name']=name
- item=query.setTag('item',attrs)
- for group in groups: item.addChild(node=Node('group',payload=[group]))
- self._owner.send(iq)
- def getItems(self):
- """ Return list of all [bare] JIDs that the roster is currently tracks."""
- return self._data.keys()
- def keys(self):
- """ Same as getItems. Provided for the sake of dictionary interface."""
- return self._data.keys()
- def __getitem__(self,item):
- """ Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster."""
- return self._data[item]
- def getItem(self,item):
- """ Get the contact in the internal format (or None if JID 'item' is not in roster)."""
- if item in self._data: return self._data[item]
- def Subscribe(self,jid):
- """ Send subscription request to JID 'jid'."""
- self._owner.send(Presence(jid,'subscribe'))
- def Unsubscribe(self,jid):
- """ Ask for removing our subscription for JID 'jid'."""
- self._owner.send(Presence(jid,'unsubscribe'))
- def Authorize(self,jid):
- """ Authorise JID 'jid'. Works only if these JID requested auth previously. """
- self._owner.send(Presence(jid,'subscribed'))
- def Unauthorize(self,jid):
- """ Unauthorise JID 'jid'. Use for declining authorisation request
- or for removing existing authorization. """
- self._owner.send(Presence(jid,'unsubscribed'))
- def getRaw(self):
- """Returns the internal data representation of the roster."""
- return self._data
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/roster_nb.py b/src/common/xmpp/roster_nb.py
index 4b373ec7e..6fa7ceda8 100644
--- a/src/common/xmpp/roster_nb.py
+++ b/src/common/xmpp/roster_nb.py
@@ -1,8 +1,8 @@
## roster_nb.py
-## based on roster.py
+## based on roster.py
##
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-## modified by Dimitur Kirov
+## modified by Dimitur Kirov
##
## 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
@@ -21,10 +21,173 @@ Simple roster implementation. Can be used though for different tasks like
mass-renaming of contacts.
'''
-from roster import Roster
-from protocol import NS_ROSTER
+from protocol import *
+from client import PlugIn
+
+import logging
+log = logging.getLogger('gajim.c.x.roster_nb')
+
+
+class NonBlockingRoster(PlugIn):
+ ''' Defines a plenty of methods that will allow you to manage roster.
+ Also automatically track presences from remote JIDs taking into
+ account that every JID can have multiple resources connected. Does not
+ currently support 'error' presences.
+ You can also use mapping interface for access to the internal representation of
+ contacts in roster.
+ '''
+ def __init__(self):
+ ''' Init internal variables. '''
+ PlugIn.__init__(self)
+ self._data = {}
+ self.set=None
+ self._exported_methods=[self.getRoster]
+
+ def Request(self,force=0):
+ ''' Request roster from server if it were not yet requested
+ (or if the 'force' argument is set). '''
+ if self.set is None: self.set=0
+ elif not force: return
+ self._owner.send(Iq('get',NS_ROSTER))
+ log.info('Roster requested from server')
+
+ def RosterIqHandler(self,dis,stanza):
+ ''' Subscription tracker. Used internally for setting items state in
+ internal roster representation. '''
+ sender = stanza.getAttr('from')
+ if not sender is None and not sender.bareMatch(
+ self._owner.User + '@' + self._owner.Server):
+ return
+ query = stanza.getTag('query')
+ if query:
+ for item in query.getTags('item'):
+ jid=item.getAttr('jid')
+ if item.getAttr('subscription')=='remove':
+ if self._data.has_key(jid): del self._data[jid]
+ return
+ log.info('Setting roster item %s...' % jid)
+ if not self._data.has_key(jid): self._data[jid]={}
+ self._data[jid]['name']=item.getAttr('name')
+ self._data[jid]['ask']=item.getAttr('ask')
+ self._data[jid]['subscription']=item.getAttr('subscription')
+ self._data[jid]['groups']=[]
+ if not self._data[jid].has_key('resources'): self._data[jid]['resources']={}
+ for group in item.getTags('group'): self._data[jid]['groups'].append(group.getData())
+ self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
+ self.set=1
+
+ def PresenceHandler(self,dis,pres):
+ ''' Presence tracker. Used internally for setting items' resources state in
+ internal roster representation. '''
+ jid=pres.getFrom()
+ if not jid:
+ # If no from attribue, it's from server
+ jid=self._owner.Server
+ jid=JID(jid)
+ if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
+ if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
+ self._data[jid.getStripped()]['resources']={}
+ item=self._data[jid.getStripped()]
+ typ=pres.getType()
+
+ if not typ:
+ log.info('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource()))
+ item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
+ if pres.getTag('show'): res['show']=pres.getShow()
+ if pres.getTag('status'): res['status']=pres.getStatus()
+ if pres.getTag('priority'): res['priority']=pres.getPriority()
+ if not pres.getTimestamp(): pres.setTimestamp()
+ res['timestamp']=pres.getTimestamp()
+ elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()]
+ # Need to handle type='error' also
+
+ def _getItemData(self,jid,dataname):
+ ''' Return specific jid's representation in internal format. Used internally. '''
+ jid=jid[:(jid+'/').find('/')]
+ return self._data[jid][dataname]
+ def _getResourceData(self,jid,dataname):
+ ''' Return specific jid's resource representation in internal format. Used internally. '''
+ if jid.find('/')+1:
+ jid,resource=jid.split('/',1)
+ if self._data[jid]['resources'].has_key(resource): return self._data[jid]['resources'][resource][dataname]
+ elif self._data[jid]['resources'].keys():
+ lastpri=-129
+ for r in self._data[jid]['resources'].keys():
+ if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])
+ return self._data[jid]['resources'][resource][dataname]
+ def delItem(self,jid):
+ ''' Delete contact 'jid' from roster.'''
+ self._owner.send(Iq('set',NS_ROSTER,payload=[Node('item',{'jid':jid,'subscription':'remove'})]))
+ def getAsk(self,jid):
+ ''' Returns 'ask' value of contact 'jid'.'''
+ return self._getItemData(jid,'ask')
+ def getGroups(self,jid):
+ ''' Returns groups list that contact 'jid' belongs to.'''
+ return self._getItemData(jid,'groups')
+ def getName(self,jid):
+ ''' Returns name of contact 'jid'.'''
+ return self._getItemData(jid,'name')
+ def getPriority(self,jid):
+ ''' Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID.'''
+ return self._getResourceData(jid,'priority')
+ def getRawRoster(self):
+ ''' Returns roster representation in internal format. '''
+ return self._data
+ def getRawItem(self,jid):
+ ''' Returns roster item 'jid' representation in internal format. '''
+ return self._data[jid[:(jid+'/').find('/')]]
+ def getShow(self, jid):
+ ''' Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID.'''
+ return self._getResourceData(jid,'show')
+ def getStatus(self, jid):
+ ''' Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID.'''
+ return self._getResourceData(jid,'status')
+ def getSubscription(self,jid):
+ ''' Returns 'subscription' value of contact 'jid'.'''
+ return self._getItemData(jid,'subscription')
+ def getResources(self,jid):
+ ''' Returns list of connected resources of contact 'jid'.'''
+ return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
+ def setItem(self,jid,name=None,groups=[]):
+ ''' Renames contact 'jid' and sets the groups list that it now belongs to.'''
+ iq=Iq('set',NS_ROSTER)
+ query=iq.getTag('query')
+ attrs={'jid':jid}
+ if name: attrs['name']=name
+ item=query.setTag('item',attrs)
+ for group in groups: item.addChild(node=Node('group',payload=[group]))
+ self._owner.send(iq)
+ def getItems(self):
+ ''' Return list of all [bare] JIDs that the roster is currently tracks.'''
+ return self._data.keys()
+ def keys(self):
+ ''' Same as getItems. Provided for the sake of dictionary interface.'''
+ return self._data.keys()
+ def __getitem__(self,item):
+ ''' Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster.'''
+ return self._data[item]
+ def getItem(self,item):
+ ''' Get the contact in the internal format (or None if JID 'item' is not in roster).'''
+ if self._data.has_key(item): return self._data[item]
+ def Subscribe(self,jid):
+ ''' Send subscription request to JID 'jid'.'''
+ self._owner.send(Presence(jid,'subscribe'))
+ def Unsubscribe(self,jid):
+ ''' Ask for removing our subscription for JID 'jid'.'''
+ self._owner.send(Presence(jid,'unsubscribe'))
+ def Authorize(self,jid):
+ ''' Authorise JID 'jid'. Works only if these JID requested auth previously. '''
+ self._owner.send(Presence(jid,'subscribed'))
+ def Unauthorize(self,jid):
+ ''' Unauthorise JID 'jid'. Use for declining authorisation request
+ or for removing existing authorization. '''
+ self._owner.send(Presence(jid,'unsubscribed'))
+ def getRaw(self):
+ '''Returns the internal data representation of the roster.'''
+ return self._data
+ # copypasted methods for roster.py from constructor to here
+
-class NonBlockingRoster(Roster):
def plugin(self, owner, request=1):
''' Register presence and subscription trackers in the owner's dispatcher.
Also request roster from server if the 'request' argument is set.
@@ -58,4 +221,4 @@ class NonBlockingRoster(Roster):
else:
return self
-# vim: se ts=3:
\ No newline at end of file
+# vim: se ts=3:
diff --git a/src/common/xmpp/session.py b/src/common/xmpp/session.py
deleted file mode 100644
index 40df45eaa..000000000
--- a/src/common/xmpp/session.py
+++ /dev/null
@@ -1,360 +0,0 @@
-##
-## XMPP server
-##
-## Copyright (C) 2004 Alexey "Snake" Nezhdanov
-##
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-
-"""
-When your handler is called it is getting the session instance as the first argument.
-This is the difference from xmpppy 0.1 where you got the "Client" instance.
-With Session class you can have "multi-session" client instead of having
-one client for each connection. Is is specifically important when you are
-writing the server.
-"""
-
-__version__="$Id"
-
-import random
-import simplexml
-from protocol import *
-
-# Transport-level flags
-SOCKET_UNCONNECTED =0
-SOCKET_ALIVE =1
-SOCKET_DEAD =2
-# XML-level flags
-STREAM__NOT_OPENED =1
-STREAM__OPENED =2
-STREAM__CLOSING =3
-STREAM__CLOSED =4
-# XMPP-session flags
-SESSION_NOT_AUTHED =1
-SESSION_AUTHED =2
-SESSION_BOUND =3
-SESSION_OPENED =4
-SESSION_CLOSED =5
-
-class Session:
- """
- The Session class instance is used for storing all session-related info like
- credentials, socket/xml stream/session state flags, roster items (in case of
- client type connection) etc.
- Session object have no means of discovering is any info is ready to be read.
- Instead you should use poll() (recomended) or select() methods for this purpose.
- Session can be one of two types: 'server' and 'client'. 'server' session handles
- inbound connection and 'client' one used to create an outbound one.
- Session instance have multitude of internal attributes. The most imporant is the 'peer' one.
- It is set once the peer is authenticated (client).
- """
- def __init__(self,socket,owner,xmlns=None,peer=None):
- """ When the session is created it's type (client/server) is determined from the beginning.
- socket argument is the pre-created socket-like object.
- It must have the following methods: send, recv, fileno, close.
- owner is the 'master' instance that have Dispatcher plugged into it and generally
- will take care about all session events.
- xmlns is the stream namespace that will be used. Client must set this argument
- If server sets this argument than stream will be dropped if opened with some another namespace.
- peer is the name of peer instance. This is the flag that differentiates client session from
- server session. Client must set it to the name of the server that will be connected, server must
- leave this argument alone.
- """
- self.xmlns=xmlns
- if peer:
- self.TYP='client'
- self.peer=peer
- self._socket_state=SOCKET_UNCONNECTED
- else:
- self.TYP='server'
- self.peer=None
- self._socket_state=SOCKET_ALIVE
- self._sock=socket
- self._send=socket.send
- self._recv=socket.recv
- self.fileno=socket.fileno
- self._registered=0
-
- self.Dispatcher=owner.Dispatcher
- self.DBG_LINE='session'
- self.DEBUG=owner.Dispatcher.DEBUG
- self._expected={}
- self._owner=owner
- if self.TYP=='server': self.ID=repr(random.random())[2:]
- else: self.ID=None
-
- self.sendbuffer=''
- self._stream_pos_queued=None
- self._stream_pos_sent=0
- self.deliver_key_queue=[]
- self.deliver_queue_map={}
- self.stanza_queue=[]
-
- self._session_state=SESSION_NOT_AUTHED
- self.waiting_features=[]
- for feature in (NS_TLS,NS_SASL,NS_BIND,NS_SESSION):
- if feature in owner.features: self.waiting_features.append(feature)
- self.features=[]
- self.feature_in_process=None
- self.slave_session=None
- self.StartStream()
-
- def StartStream(self):
- """ This method is used to initialise the internal xml expat parser
- and to send initial stream header (in case of client connection).
- Should be used after initial connection and after every stream restart."""
- self._stream_state=STREAM__NOT_OPENED
- self.Stream=simplexml.NodeBuilder()
- self.Stream._dispatch_depth=2
- self.Stream.dispatch=self._dispatch
- self.Parse=self.Stream.Parse
- self.Stream.stream_footer_received=self._stream_close
- if self.TYP=='client':
- self.Stream.stream_header_received=self._catch_stream_id
- self._stream_open()
- else:
- self.Stream.stream_header_received=self._stream_open
-
- def receive(self):
- """ Reads all pending incoming data.
- Raises IOError on disconnection.
- Blocks until at least one byte is read."""
- try:
- received = self._recv(10240)
- except socket.error:
- received = ''
-
- if len(received): # length of 0 means disconnect
- self.DEBUG(repr(self.fileno())+' '+received,'got')
- else:
- self.DEBUG('Socket error while receiving data','error')
- self.set_socket_state(SOCKET_DEAD)
- raise IOError("Peer disconnected")
- return received
-
- def sendnow(self,chunk):
- """ Put chunk into "immidiatedly send" queue.
- Should only be used for auth/TLS stuff and like.
- If you just want to shedule regular stanza for delivery use enqueue method.
- """
- if isinstance(chunk,Node): chunk = chunk.__str__().encode('utf-8')
- elif isinstance(chunk, unicode): chunk = chunk.encode('utf-8')
- self.enqueue(chunk)
-
- def enqueue(self,stanza):
- """ Takes Protocol instance as argument.
- Puts stanza into "send" fifo queue. Items into the send queue are hold until
- stream authenticated. After that this method is effectively the same as "sendnow" method."""
- if isinstance(stanza,Protocol):
- self.stanza_queue.append(stanza)
- else: self.sendbuffer+=stanza
- if self._socket_state>=SOCKET_ALIVE: self.push_queue()
-
- def push_queue(self,failreason=ERR_RECIPIENT_UNAVAILABLE):
- """ If stream is authenticated than move items from "send" queue to "immidiatedly send" queue.
- Else if the stream is failed then return all queued stanzas with error passed as argument.
- Otherwise do nothing."""
- # If the stream authed - convert stanza_queue into sendbuffer and set the checkpoints
-
- if self._stream_state>=STREAM__CLOSED or self._socket_state>=SOCKET_DEAD: # the stream failed. Return all stanzas that are still waiting for delivery.
- self._owner.deactivatesession(self)
- for key in self.deliver_key_queue: # Not sure. May be I
- self._dispatch(Error(self.deliver_queue_map[key],failreason),trusted=1) # should simply re-dispatch it?
- for stanza in self.stanza_queue: # But such action can invoke
- self._dispatch(Error(stanza,failreason),trusted=1) # Infinite loops in case of S2S connection...
- self.deliver_queue_map,self.deliver_key_queue,self.stanza_queue={},[],[]
- return
- elif self._session_state>=SESSION_AUTHED: # FIXME! - .
- #### LOCK_QUEUE
- for stanza in self.stanza_queue:
- txt=stanza.__str__().encode('utf-8')
- self.sendbuffer+=txt
- self._stream_pos_queued+=len(txt) # should be re-evaluated for SSL connection.
- self.deliver_queue_map[self._stream_pos_queued]=stanza # position of the stream when stanza will be successfully and fully sent
- self.deliver_key_queue.append(self._stream_pos_queued)
- self.stanza_queue=[]
- #### UNLOCK_QUEUE
-
- def flush_queue(self):
- """ Put the "immidiatedly send" queue content on the wire. Blocks until at least one byte sent."""
- if self.sendbuffer:
- try:
- # LOCK_QUEUE
- sent=self._send(self.sendbuffer) # blocking socket
- except Exception:
- # UNLOCK_QUEUE
- self.set_socket_state(SOCKET_DEAD)
- self.DEBUG("Socket error while sending data",'error')
- return self.terminate_stream()
- self.DEBUG(repr(self.fileno())+' '+self.sendbuffer[:sent],'sent')
- self._stream_pos_sent+=sent
- self.sendbuffer=self.sendbuffer[sent:]
- self._stream_pos_delivered=self._stream_pos_sent # Should be acquired from socket somehow. Take SSL into account.
- while self.deliver_key_queue and self._stream_pos_delivered>self.deliver_key_queue[0]:
- del self.deliver_queue_map[self.deliver_key_queue[0]]
- self.deliver_key_queue.remove(self.deliver_key_queue[0])
- # UNLOCK_QUEUE
-
- def _dispatch(self,stanza,trusted=0):
- """ This is callback that is used to pass the received stanza forth to owner's dispatcher
- _if_ the stream is authorised. Otherwise the stanza is just dropped.
- The 'trusted' argument is used to emulate stanza receive.
- This method is used internally.
- """
- self._owner.packets+=1
- print self._owner.packets
- if self._stream_state==STREAM__OPENED or trusted: # if the server really should reject all stanzas after he is closed stream (himeself)?
- self.DEBUG(stanza.__str__(),'dispatch')
- stanza.trusted=trusted
- return self.Dispatcher.dispatch(stanza,self)
-
- def _catch_stream_id(self,ns=None,tag='stream',attrs={}):
- """ This callback is used to detect the stream namespace of incoming stream. Used internally. """
- if 'id' not in attrs or not attrs['id']:
- return self.terminate_stream(STREAM_INVALID_XML)
- self.ID=attrs['id']
- if 'version' not in attrs: self._owner.Dialback(self)
-
- def _stream_open(self,ns=None,tag='stream',attrs={}):
- """ This callback is used to handle opening stream tag of the incoming stream.
- In the case of client session it just make some validation.
- Server session also sends server headers and if the stream valid the features node.
- Used internally. """
- text='\n')
- self.set_stream_state(STREAM__OPENED)
- if self.TYP=='client': return
- if tag!='stream': return self.terminate_stream(STREAM_INVALID_XML)
- if ns!=NS_STREAMS: return self.terminate_stream(STREAM_INVALID_NAMESPACE)
- if self.Stream.xmlns!=self.xmlns: return self.terminate_stream(STREAM_BAD_NAMESPACE_PREFIX)
- if 'to' not in attrs: return self.terminate_stream(STREAM_IMPROPER_ADDRESSING)
- if attrs['to'] not in self._owner.servernames: return self.terminate_stream(STREAM_HOST_UNKNOWN)
- self.ourname=attrs['to'].lower()
- if self.TYP=='server' and 'version' in attrs:
- # send features
- features=Node('stream:features')
- if NS_TLS in self.waiting_features:
- features.T.starttls.setNamespace(NS_TLS)
- if NS_SASL in self.waiting_features:
- features.T.mechanisms.setNamespace(NS_SASL)
- for mec in self._owner.SASL.mechanisms:
- features.T.mechanisms.NT.mechanism=mec
- else:
- if NS_BIND in self.waiting_features: features.T.bind.setNamespace(NS_BIND)
- if NS_SESSION in self.waiting_features: features.T.session.setNamespace(NS_SESSION)
- self.sendnow(features)
-
- def feature(self,feature):
- """ Declare some stream feature as activated one. """
- if feature not in self.features: self.features.append(feature)
- self.unfeature(feature)
-
- def unfeature(self,feature):
- """ Declare some feature as illegal. Illegal features can not be used.
- Example: BIND feature becomes illegal after Non-SASL auth. """
- if feature in self.waiting_features: self.waiting_features.remove(feature)
-
- def _stream_close(self,unregister=1):
- """ Write the closing stream tag and destroy the underlaying socket. Used internally. """
- if self._stream_state>=STREAM__CLOSED: return
- self.set_stream_state(STREAM__CLOSING)
- self.sendnow('')
- self.set_stream_state(STREAM__CLOSED)
- self.push_queue() # decompose queue really since STREAM__CLOSED
- self._owner.flush_queues()
- if unregister: self._owner.unregistersession(self)
- self._destroy_socket()
-
- def terminate_stream(self,error=None,unregister=1):
- """ Notify the peer about stream closure.
- Ensure that xmlstream is not brokes - i.e. if the stream isn't opened yet -
- open it before closure.
- If the error condition is specified than create a stream error and send it along with
- closing stream tag.
- Emulate receiving 'unavailable' type presence just before stream closure.
- """
- if self._stream_state>=STREAM__CLOSING: return
- if self._stream_state=SESSION_AUTHED: self._stream_pos_queued=self._stream_pos_sent
- self._session_state=newstate
-
- def set_stream_state(self,newstate):
- """ Change the underlaying XML stream state
- Stream starts with STREAM__NOT_OPENED and then proceeds with
- STREAM__OPENED, STREAM__CLOSING and STREAM__CLOSED states.
- Note that some features (like TLS and SASL)
- requires stream re-start so this state can have non-linear changes. """
- if self._stream_state " replaced by their respective XML entities."""
+ '''Returns provided string with symbols & < > " replaced by their respective XML entities.'''
# replace also FORM FEED and ESC, because they are not valid XML chars
return txt.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace(u'\x0C', "").replace(u'\x1B', "")
ENCODING='utf-8'
def ustr(what):
- """Converts object "what" to unicode string using it's own __str__ method if accessible or unicode method otherwise."""
+ '''Converts object "what" to unicode string using it's own __str__ method if accessible or unicode method otherwise.'''
if isinstance(what, unicode): return what
try: r=what.__str__()
except AttributeError: r=str(what)
@@ -34,7 +36,7 @@ def ustr(what):
return r
class Node(object):
- """ Node class describes syntax of separate XML Node. It have a constructor that permits node creation
+ ''' Node class describes syntax of separate XML Node. It have a constructor that permits node creation
from set of "namespace name", attributes and payload of text strings and other nodes.
It does not natively support building node from text string and uses NodeBuilder class for that purpose.
After creation node can be mangled in many ways so it can be completely changed.
@@ -47,16 +49,16 @@ class Node(object):
info with the "original" node that is changing the one node may influence the other. Though it is
rarely needed (in xmpppy it is never needed at all since I'm usually never using original node after
replication (and using replication only to move upwards on the classes tree).
- """
+ '''
FORCE_NODE_RECREATION=0
def __init__(self, tag=None, attrs={}, payload=[], parent=None, nsp=None, node_built=False, node=None):
- """ Takes "tag" argument as the name of node (prepended by namespace, if needed and separated from it
+ ''' Takes "tag" argument as the name of node (prepended by namespace, if needed and separated from it
by a space), attrs dictionary as the set of arguments, payload list as the set of textual strings
and child nodes that this node carries within itself and "parent" argument that is another node
that this one will be the child of. Also the __init__ can be provided with "node" argument that is
either a text string containing exactly one node or another Node instance to begin with. If both
"node" and other arguments is provided then the node initially created as replica of "node"
- provided and then modified to be compliant with other arguments."""
+ provided and then modified to be compliant with other arguments.'''
if node:
if self.FORCE_NODE_RECREATION and isinstance(node, Node):
node=str(node)
@@ -108,8 +110,8 @@ class Node(object):
return ns
def __str__(self,fancy=0):
- """ Method used to dump node into textual representation.
- if "fancy" argument is set to True produces indented output for readability."""
+ ''' Method used to dump node into textual representation.
+ if "fancy" argument is set to True produces indented output for readability.'''
s = (fancy-1) * 2 * ' ' + "<" + self.name
if self.namespace:
if not self.parent or self.parent.namespace!=self.namespace:
@@ -125,7 +127,10 @@ class Node(object):
for a in self.kids:
if not fancy and (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt])
elif (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt].strip())
- s = s + a.__str__(fancy and fancy+1)
+ if isinstance(a, str) or isinstance(a, unicode):
+ s = s + a.__str__()
+ else:
+ s = s + a.__str__(fancy and fancy+1)
cnt=cnt+1
if not fancy and (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt])
elif (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt].strip())
@@ -138,8 +143,8 @@ class Node(object):
if fancy: s = s + "\n"
return s
def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None):
- """ If "node" argument is provided, adds it as child node. Else creates new node from
- the other arguments' values and adds it as well."""
+ ''' If "node" argument is provided, adds it as child node. Else creates new node from
+ the other arguments' values and adds it as well.'''
if node:
newnode=node
node.parent = self
@@ -149,45 +154,45 @@ class Node(object):
self.kids.append(newnode)
return newnode
def addData(self, data):
- """ Adds some CDATA to node. """
+ ''' Adds some CDATA to node. '''
self.data.append(ustr(data))
def clearData(self):
- """ Removes all CDATA from the node. """
+ ''' Removes all CDATA from the node. '''
self.data=[]
def delAttr(self, key):
- """ Deletes an attribute "key" """
+ ''' Deletes an attribute "key" '''
del self.attrs[key]
def delChild(self, node, attrs={}):
- """ Deletes the "node" from the node's childs list, if "node" is an instance.
- Else deletes the first node that have specified name and (optionally) attributes. """
+ ''' Deletes the "node" from the node's childs list, if "node" is an instance.
+ Else deletes the first node that have specified name and (optionally) attributes. '''
if not isinstance(node, Node): node=self.getTag(node,attrs)
self.kids.remove(node)
return node
def getAttrs(self):
- """ Returns all node's attributes as dictionary. """
+ ''' Returns all node's attributes as dictionary. '''
return self.attrs
def getAttr(self, key):
- """ Returns value of specified attribute. """
+ ''' Returns value of specified attribute. '''
return self.attrs.get(key)
def getChildren(self):
- """ Returns all node's child nodes as list. """
+ ''' Returns all node's child nodes as list. '''
return self.kids
def getData(self):
- """ Returns all node CDATA as string (concatenated). """
+ ''' Returns all node CDATA as string (concatenated). '''
return ''.join(self.data)
def getName(self):
- """ Returns the name of node """
+ ''' Returns the name of node '''
return self.name
def getNamespace(self):
- """ Returns the namespace of node """
+ ''' Returns the namespace of node '''
return self.namespace
def getParent(self):
- """ Returns the parent of node (if present). """
+ ''' Returns the parent of node (if present). '''
return self.parent
def getPayload(self):
- """ Return the payload of node i.e. list of child nodes and CDATA entries.
+ ''' Return the payload of node i.e. list of child nodes and CDATA entries.
F.e. for "text1 text2" will be returned list:
- ['text1', , , ' text2']. """
+ ['text1', , , ' text2']. '''
ret=[]
for i in range(len(self.kids)+len(self.data)+1):
try:
@@ -197,24 +202,24 @@ class Node(object):
except IndexError: pass
return ret
def getTag(self, name, attrs={}, namespace=None):
- """ Filters all child nodes using specified arguments as filter.
- Returns the first found or None if not found. """
+ ''' Filters all child nodes using specified arguments as filter.
+ Returns the first found or None if not found. '''
return self.getTags(name, attrs, namespace, one=1)
def getTagAttr(self,tag,attr):
- """ Returns attribute value of the child with specified name (or None if no such attribute)."""
+ ''' Returns attribute value of the child with specified name (or None if no such attribute).'''
try:
return self.getTag(tag).attrs[attr]
except:
return None
def getTagData(self,tag):
- """ Returns cocatenated CDATA of the child with specified name."""
+ ''' Returns cocatenated CDATA of the child with specified name.'''
try:
return self.getTag(tag).getData()
except Exception:
return None
def getTags(self, name, attrs={}, namespace=None, one=0):
- """ Filters all child nodes using specified arguments as filter.
- Returns the list of nodes found. """
+ ''' Filters all child nodes using specified arguments as filter.
+ Returns the list of nodes found. '''
nodes=[]
for node in self.kids:
if namespace and namespace!=node.getNamespace(): continue
@@ -226,7 +231,7 @@ class Node(object):
if not one: return nodes
def iterTags(self, name, attrs={}, namespace=None):
- """ Iterate over all children using specified arguments as filter. """
+ ''' Iterate over all children using specified arguments as filter. '''
for node in self.kids:
if namespace is not None and namespace!=node.getNamespace(): continue
if node.getName() == name:
@@ -237,61 +242,61 @@ class Node(object):
yield node
def setAttr(self, key, val):
- """ Sets attribute "key" with the value "val". """
+ ''' Sets attribute "key" with the value "val". '''
self.attrs[key]=val
def setData(self, data):
- """ Sets node's CDATA to provided string. Resets all previous CDATA!"""
+ ''' Sets node's CDATA to provided string. Resets all previous CDATA!'''
self.data=[ustr(data)]
def setName(self,val):
- """ Changes the node name. """
+ ''' Changes the node name. '''
self.name = val
def setNamespace(self, namespace):
- """ Changes the node namespace. """
+ ''' Changes the node namespace. '''
self.namespace=namespace
def setParent(self, node):
- """ Sets node's parent to "node". WARNING: do not checks if the parent already present
- and not removes the node from the list of childs of previous parent. """
+ ''' Sets node's parent to "node". WARNING: do not checks if the parent already present
+ and not removes the node from the list of childs of previous parent. '''
self.parent = node
def setPayload(self,payload,add=0):
- """ Sets node payload according to the list specified. WARNING: completely replaces all node's
- previous content. If you wish just to add child or CDATA - use addData or addChild methods. """
+ ''' Sets node payload according to the list specified. WARNING: completely replaces all node's
+ previous content. If you wish just to add child or CDATA - use addData or addChild methods. '''
if isinstance(payload, basestring): payload=[payload]
if add: self.kids+=payload
else: self.kids=payload
def setTag(self, name, attrs={}, namespace=None):
- """ Same as getTag but if the node with specified namespace/attributes not found, creates such
- node and returns it. """
+ ''' Same as getTag but if the node with specified namespace/attributes not found, creates such
+ node and returns it. '''
node=self.getTags(name, attrs, namespace=namespace, one=1)
if node: return node
else: return self.addChild(name, attrs, namespace=namespace)
def setTagAttr(self,tag,attr,val):
- """ Creates new node (if not already present) with name "tag"
- and sets it's attribute "attr" to value "val". """
+ ''' Creates new node (if not already present) with name "tag"
+ and sets it's attribute "attr" to value "val". '''
try:
self.getTag(tag).attrs[attr]=val
except Exception:
self.addChild(tag,attrs={attr:val})
def setTagData(self,tag,val,attrs={}):
- """ Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs"
- and sets it's CDATA to string "val". """
+ ''' Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs"
+ and sets it's CDATA to string "val". '''
try:
self.getTag(tag,attrs).setData(ustr(val))
except Exception:
self.addChild(tag,attrs,payload=[ustr(val)])
def has_attr(self,key):
- """ Checks if node have attribute "key"."""
+ ''' Checks if node have attribute "key".'''
return key in self.attrs
def __getitem__(self,item):
- """ Returns node's attribute "item" value. """
+ ''' Returns node's attribute "item" value. '''
return self.getAttr(item)
def __setitem__(self,item,val):
- """ Sets node's attribute "item" value. """
+ ''' Sets node's attribute "item" value. '''
return self.setAttr(item,val)
def __delitem__(self,item):
- """ Deletes node's attribute "item". """
+ ''' Deletes node's attribute "item". '''
return self.delAttr(item)
def __getattr__(self,attr):
- """ Reduce memory usage caused by T/NT classes - use memory only when needed. """
+ ''' Reduce memory usage caused by T/NT classes - use memory only when needed. '''
if attr=='T':
self.T=T(self)
return self.T
@@ -301,7 +306,7 @@ class Node(object):
raise AttributeError
class T:
- """ Auxiliary class used to quick access to node's child nodes. """
+ ''' Auxiliary class used to quick access to node's child nodes. '''
def __init__(self,node): self.__dict__['node']=node
def __getattr__(self,attr): return self.node.setTag(attr)
def __setattr__(self,attr,val):
@@ -310,28 +315,26 @@ class T:
def __delattr__(self,attr): return self.node.delChild(attr)
class NT(T):
- """ Auxiliary class used to quick create node's child nodes. """
+ ''' Auxiliary class used to quick create node's child nodes. '''
def __getattr__(self,attr): return self.node.addChild(attr)
def __setattr__(self,attr,val):
if isinstance(val,Node): self.node.addChild(attr,node=val)
else: return self.node.addChild(attr,payload=[val])
-DBG_NODEBUILDER = 'nodebuilder'
class NodeBuilder:
- """ Builds a Node class minidom from data parsed to it. This class used for two purposes:
+ ''' Builds a Node class minidom from data parsed to it. This class used for two purposes:
1. Creation an XML Node from a textual representation. F.e. reading a config file. See an XML2Node method.
2. Handling an incoming XML stream. This is done by mangling
the __dispatch_depth parameter and redefining the dispatch method.
- You do not need to use this class directly if you do not designing your own XML handler."""
+ You do not need to use this class directly if you do not designing your own XML handler.'''
def __init__(self,data=None,initial_node=None):
- """ Takes two optional parameters: "data" and "initial_node".
+ ''' Takes two optional parameters: "data" and "initial_node".
By default class initialised with empty Node class instance.
Though, if "initial_node" is provided it used as "starting point".
You can think about it as of "node upgrade".
"data" (if provided) feeded to parser immidiatedly after instance init.
- """
- self.DEBUG(DBG_NODEBUILDER, "Preparing to handle incoming XML stream.", 'start')
-
+ '''
+ log.debug("Preparing to handle incoming XML stream.")
self._parser = xml.parsers.expat.ParserCreate()
self._parser.StartElementHandler = self.starttag
self._parser.EndElementHandler = self.endtag
@@ -360,7 +363,7 @@ class NodeBuilder:
self.data_buffer = None
def destroy(self):
- """ Method used to allow class instance to be garbage-collected. """
+ ''' Method used to allow class instance to be garbage-collected. '''
self.check_data_buffer()
self._parser.StartElementHandler = None
self._parser.EndElementHandler = None
@@ -368,10 +371,10 @@ class NodeBuilder:
self._parser.StartNamespaceDeclHandler = None
def starttag(self, tag, attrs):
- """XML Parser callback. Used internally"""
+ '''XML Parser callback. Used internally'''
self.check_data_buffer()
self._inc_depth()
- self.DEBUG(DBG_NODEBUILDER, "DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, repr(attrs)), 'down')
+ log.info("STARTTAG.. DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`))
if self.__depth == self._dispatch_depth:
if not self._mini_dom :
self._mini_dom = Node(tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True)
@@ -402,15 +405,15 @@ class NodeBuilder:
self._ptr.parent.data.append('')
self.last_is_data = 0
def endtag(self, tag ):
- """XML Parser callback. Used internally"""
- self.DEBUG(DBG_NODEBUILDER, "DEPTH -> %i , tag -> %s" % (self.__depth, tag), 'up')
+ '''XML Parser callback. Used internally'''
+ log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag))
self.check_data_buffer()
if self.__depth == self._dispatch_depth:
self.dispatch(self._mini_dom)
elif self.__depth > self._dispatch_depth:
self._ptr = self._ptr.parent
else:
- self.DEBUG(DBG_NODEBUILDER, "Got higher than dispatch level. Stream terminated?", 'stop')
+ log.info("Got higher than dispatch level. Stream terminated?")
self._dec_depth()
self.last_is_data = 0
if self.__depth == 0: self.stream_footer_received()
@@ -424,26 +427,25 @@ class NodeBuilder:
self.last_is_data = 1
def handle_namespace_start(self, prefix, uri):
- """XML Parser callback. Used internally"""
+ '''XML Parser callback. Used internally'''
self.check_data_buffer()
- def DEBUG(self, level, text, comment=None):
- """ Gets all NodeBuilder walking events. Can be used for debugging if redefined."""
+
def getDom(self):
- """ Returns just built Node. """
+ ''' Returns just built Node. '''
self.check_data_buffer()
return self._mini_dom
def dispatch(self,stanza):
- """ Gets called when the NodeBuilder reaches some level of depth on it's way up with the built
- node as argument. Can be redefined to convert incoming XML stanzas to program events. """
+ ''' Gets called when the NodeBuilder reaches some level of depth on it's way up with the built
+ node as argument. Can be redefined to convert incoming XML stanzas to program events. '''
def stream_header_received(self,ns,tag,attrs):
- """ Method called when stream just opened. """
+ ''' Method called when stream just opened. '''
self.check_data_buffer()
def stream_footer_received(self):
- """ Method called when stream just closed. """
+ ''' Method called when stream just closed. '''
self.check_data_buffer()
def has_received_endtag(self, level=0):
- """ Return True if at least one end tag was seen (at level) """
+ ''' Return True if at least one end tag was seen (at level) '''
return self.__depth <= level and self.__max_depth > level
def _inc_depth(self):
@@ -456,14 +458,14 @@ class NodeBuilder:
self.__depth -= 1
def XML2Node(xml):
- """ Converts supplied textual string into XML node. Handy f.e. for reading configuration file.
- Raises xml.parser.expat.parsererror if provided string is not well-formed XML. """
+ ''' Converts supplied textual string into XML node. Handy f.e. for reading configuration file.
+ Raises xml.parser.expat.parsererror if provided string is not well-formed XML. '''
return NodeBuilder(xml).getDom()
def BadXML2Node(xml):
- """ Converts supplied textual string into XML node. Survives if xml data is cutted half way round.
+ ''' Converts supplied textual string into XML node. Survives if xml data is cutted half way round.
I.e. "some text
some more text". Will raise xml.parser.expat.parsererror on misplaced
- tags though. F.e. "some text
some more text" will not work."""
+ tags though. F.e. "some text
some more text" will not work.'''
return NodeBuilder(xml).getDom()
# vim: se ts=3:
diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py
new file mode 100644
index 000000000..67c456f41
--- /dev/null
+++ b/src/common/xmpp/tls_nb.py
@@ -0,0 +1,375 @@
+## tls_nb.py
+## based on transports_nb.py
+##
+## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
+## modified by Dimitur Kirov
+## modified by Tomas Karasek
+##
+## 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; either version 2, or (at your option)
+## any later version.
+##
+## 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 socket
+from client import PlugIn
+from protocol import *
+
+import sys
+import os
+import errno
+import time
+
+import traceback
+
+import logging
+log = logging.getLogger('gajim.c.x.tls_nb')
+
+USE_PYOPENSSL = False
+
+PYOPENSSL = 'PYOPENSSL'
+PYSTDLIB = 'PYSTDLIB'
+
+try:
+ #raise ImportError("Manually disabled PyOpenSSL")
+ import OpenSSL.SSL
+ import OpenSSL.crypto
+ USE_PYOPENSSL = True
+ log.info("PyOpenSSL loaded")
+except ImportError:
+ log.debug("Import of PyOpenSSL failed:", exc_info=True)
+
+ # FIXME: Remove these prints before release, replace with a warning dialog.
+ print >> sys.stderr, "=" * 79
+ print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
+ print >> sys.stderr, "=" * 79
+
+
+def torf(cond, tv, fv):
+ if cond: return tv
+ return fv
+
+def gattr(obj, attr, default=None):
+ try:
+ return getattr(obj, attr)
+ except:
+ return default
+
+class SSLWrapper:
+ class Error(IOError):
+ def __init__(self, sock=None, exc=None, errno=None, strerror=None, peer=None):
+ self.parent = IOError
+
+ errno = errno or gattr(exc, 'errno')
+ strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
+ if not isinstance(strerror, basestring): strerror = repr(strerror)
+
+ self.sock = sock
+ self.exc = exc
+ self.peer = peer
+ self.exc_name = None
+ self.exc_args = None
+ self.exc_str = None
+ self.exc_repr = None
+
+ if self.exc is not None:
+ self.exc_name = str(self.exc.__class__)
+ self.exc_args = gattr(self.exc, 'args')
+ self.exc_str = str(self.exc)
+ self.exc_repr = repr(self.exc)
+ if not errno:
+ try:
+ if isinstance(exc, OpenSSL.SSL.SysCallError):
+ if self.exc_args[0] > 0:
+ errno = self.exc_args[0]
+ strerror = self.exc_args[1]
+ except: pass
+
+ self.parent.__init__(self, errno, strerror)
+
+ if self.peer is None and sock is not None:
+ try:
+ ppeer = self.sock.getpeername()
+ if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
+ and isinstance(ppeer[1], int):
+ self.peer = ppeer
+ except: pass
+
+ def __str__(self):
+ s = str(self.__class__)
+ if self.peer: s += " for %s:%d" % self.peer
+ if self.errno is not None: s += ": [Errno: %d]" % self.errno
+ if self.strerror: s += " (%s)" % self.strerror
+ if self.exc_name:
+ s += ", Caused by %s" % self.exc_name
+ if self.exc_str:
+ if self.strerror: s += "(%s)" % self.exc_str
+ else: s += "(%s)" % str(self.exc_args)
+ return s
+
+ def __init__(self, sslobj, sock=None):
+ self.sslobj = sslobj
+ self.sock = sock
+ log.debug("%s.__init__ called with %s", self.__class__, sslobj)
+
+ def recv(self, data, flags=None):
+ ''' Receive wrapper for SSL object
+
+ We can return None out of this function to signal that no data is
+ available right now. Better than an exception, which differs
+ depending on which SSL lib we're using. Unfortunately returning ''
+ can indicate that the socket has been closed, so to be sure, we avoid
+ this by returning None. '''
+
+ raise NotImplementedException()
+
+ def send(self, data, flags=None, now = False):
+ raise NotImplementedException()
+
+class PyOpenSSLWrapper(SSLWrapper):
+ '''Wrapper class for PyOpenSSL's recv() and send() methods'''
+
+ def __init__(self, *args):
+ self.parent = SSLWrapper
+ self.parent.__init__(self, *args)
+
+ def is_numtoolarge(self, e):
+ t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large')
+ return isinstance(e.args, (list, tuple)) and len(e.args) == 1 and \
+ isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and \
+ e.args[0][0] == e.args[0][1] == t
+
+ def recv(self, bufsize, flags=None):
+ retval = None
+ try:
+ if flags is None: retval = self.sslobj.recv(bufsize)
+ else: retval = self.sslobj.recv(bufsize, flags)
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ log.debug("Recv: Want-error: " + repr(e))
+ except OpenSSL.SSL.SysCallError, e:
+ log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
+ #traceback.print_exc()
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ except OpenSSL.SSL.Error, e:
+ if self.is_numtoolarge(e):
+ # warn, but ignore this exception
+ log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
+ else:
+ log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
+ #traceback.print_exc()
+ #print "Current Stack:"
+ #traceback.print_stack()
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return retval
+
+ def send(self, data, flags=None, now = False):
+ try:
+ if flags is None: return self.sslobj.send(data)
+ else: return self.sslobj.send(data, flags)
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ #log.debug("Send: " + repr(e))
+ time.sleep(0.1) # prevent 100% CPU usage
+ except OpenSSL.SSL.SysCallError, e:
+ log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
+ #traceback.print_exc()
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ except OpenSSL.SSL.Error, e:
+ if self.is_numtoolarge(e):
+ # warn, but ignore this exception
+ log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
+ else:
+ log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True)
+ #traceback.print_exc()
+ #print "Current Stack:"
+ #traceback.print_stack()
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return 0
+
+class StdlibSSLWrapper(SSLWrapper):
+ '''Wrapper class for Python socket.ssl read() and write() methods'''
+
+ def __init__(self, *args):
+ self.parent = SSLWrapper
+ self.parent.__init__(self, *args)
+
+ def recv(self, bufsize, flags=None):
+ # we simply ignore flags since ssl object doesn't support it
+ try:
+ return self.sslobj.read(bufsize)
+ except socket.sslerror, e:
+ log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True)
+ if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return None
+
+ def send(self, data, flags=None, now = False):
+ # we simply ignore flags since ssl object doesn't support it
+ try:
+ return self.sslobj.write(data)
+ except socket.sslerror, e:
+ log.debug("Send: Caught socket.sslerror:", exc_info=True)
+ if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return 0
+
+
+class NonBlockingTLS(PlugIn):
+ ''' TLS connection used to encrypts already estabilished tcp connection.'''
+
+ def __init__(self, cacerts, mycerts):
+ '''
+ :param cacerts: path to pem file with certificates of known XMPP servers
+ :param mycerts: path to pem file with certificates of user trusted servers
+ '''
+ PlugIn.__init__(self)
+ self.cacerts = cacerts
+ self.mycerts = mycerts
+
+ # from ssl.h (partial extract)
+ ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
+ "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
+ "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08,
+ "SSL_CB_ALERT": 0x4000,
+ "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
+
+ def PlugIn(self, owner):
+ '''
+ start using encryption immediately
+ '''
+ log.info('Starting TLS estabilishing')
+ PlugIn.PlugIn(self, owner)
+ try:
+ res = self._startSSL()
+ except Exception, e:
+ log.error("PlugIn: while trying _startSSL():", exc_info=True)
+ return False
+ return res
+
+
+
+ def _dumpX509(self, cert, stream=sys.stderr):
+ print >> stream, "Digest (SHA-1):", cert.digest("sha1")
+ print >> stream, "Digest (MD5):", cert.digest("md5")
+ print >> stream, "Serial #:", cert.get_serial_number()
+ print >> stream, "Version:", cert.get_version()
+ print >> stream, "Expired:", torf(cert.has_expired(), "Yes", "No")
+ print >> stream, "Subject:"
+ self._dumpX509Name(cert.get_subject(), stream)
+ print >> stream, "Issuer:"
+ self._dumpX509Name(cert.get_issuer(), stream)
+ self._dumpPKey(cert.get_pubkey(), stream)
+
+ def _dumpX509Name(self, name, stream=sys.stderr):
+ print >> stream, "X509Name:", str(name)
+
+ def _dumpPKey(self, pkey, stream=sys.stderr):
+ typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", OpenSSL.crypto.TYPE_DSA: "DSA"}
+ print >> stream, "PKey bits:", pkey.bits()
+ print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), "Unknown"), pkey.type())
+
+ def _startSSL(self):
+ ''' Immediatedly switch socket to TLS mode. Used internally.'''
+ log.debug("_startSSL called")
+
+ if USE_PYOPENSSL: result = self._startSSL_pyOpenSSL()
+ else: result = self._startSSL_stdlib()
+
+ if result:
+ log.debug("Synchronous handshake completed")
+ return True
+ else:
+ return False
+
+
+ def _startSSL_pyOpenSSL(self):
+ log.debug("_startSSL_pyOpenSSL called")
+ tcpsock = self._owner
+ # FIXME: should method be configurable?
+ #tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
+ tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+ tcpsock.ssl_errnum = 0
+ tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
+ try:
+ tcpsock._sslContext.load_verify_locations(self.cacerts)
+ except:
+ log.warning('Unable to load SSL certificates from file %s' % \
+ os.path.abspath(self.cacerts))
+ # load users certs
+ if os.path.isfile(self.mycerts):
+ store = tcpsock._sslContext.get_cert_store()
+ f = open(self.mycerts)
+ lines = f.readlines()
+ i = 0
+ begin = -1
+ for line in lines:
+ if 'BEGIN CERTIFICATE' in line:
+ begin = i
+ elif 'END CERTIFICATE' in line and begin > -1:
+ cert = ''.join(lines[begin:i+2])
+ try:
+ X509cert = OpenSSL.crypto.load_certificate(
+ OpenSSL.crypto.FILETYPE_PEM, cert)
+ store.add_cert(X509cert)
+ except OpenSSL.crypto.Error, exception_obj:
+ log.warning('Unable to load a certificate from file %s: %s' %\
+ (self.mycerts, exception_obj.args[0][0][2]))
+ except:
+ log.warning('Unknown error while loading certificate from file %s' %\
+ self.mycerts)
+ begin = -1
+ i += 1
+ tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
+ tcpsock._sslObj.set_connect_state() # set to client mode
+
+ wrapper = PyOpenSSLWrapper(tcpsock._sslObj)
+ tcpsock._recv = wrapper.recv
+ tcpsock._send = wrapper.send
+
+ log.debug("Initiating handshake...")
+ tcpsock._sslObj.setblocking(True)
+ try:
+ tcpsock._sslObj.do_handshake()
+ except:
+ log.error('Error while TLS handshake: ', exc_info=True)
+ return False
+ tcpsock._sslObj.setblocking(False)
+ self._owner.ssl_lib = PYOPENSSL
+ return True
+
+
+ def _startSSL_stdlib(self):
+ log.debug("_startSSL_stdlib called")
+ tcpsock=self._owner
+ try:
+ tcpsock._sock.setblocking(True)
+ tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
+ tcpsock._sock.setblocking(False)
+ tcpsock._sslIssuer = tcpsock._sslObj.issuer()
+ tcpsock._sslServer = tcpsock._sslObj.server()
+ wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
+ tcpsock._recv = wrapper.recv
+ tcpsock._send = wrapper.send
+ except:
+ log.error("Exception caught in _startSSL_stdlib:", exc_info=True)
+ return False
+ self._owner.ssl_lib = PYSTDLIB
+ return True
+
+ def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
+ # Exceptions can't propagate up through this callback, so print them here.
+ try:
+ self._owner.ssl_fingerprint_sha1 = cert.digest('sha1')
+ if errnum == 0:
+ return True
+ self._owner.ssl_errnum = errnum
+ self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
+ OpenSSL.crypto.FILETYPE_PEM, cert)
+ return True
+ except:
+ log.error("Exception caught in _ssl_info_callback:", exc_info=True)
+ traceback.print_exc() # Make sure something is printed, even if log is disabled.
+
diff --git a/src/common/xmpp/transports.py b/src/common/xmpp/transports.py
deleted file mode 100644
index 9fd83884a..000000000
--- a/src/common/xmpp/transports.py
+++ /dev/null
@@ -1,292 +0,0 @@
-## transports.py
-##
-## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
-##
-## 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; either version 2, or (at your option)
-## any later version.
-##
-## 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.
-
-# $Id: transports.py,v 1.20 2005/05/12 07:35:55 snakeru Exp $
-
-"""
-This module contains the low-level implementations of xmpppy connect methods or
-(in other words) transports for xmpp-stanzas.
-Currently here is three transports:
-direct TCP connect - TCPsocket class
-proxied TCP connect - HTTPPROXYsocket class (CONNECT proxies)
-TLS connection - TLS class. Can be used for SSL connections also.
-
-Transports are stackable so you - f.e. TLS use HTPPROXYsocket or TCPsocket as more low-level transport.
-
-Also exception 'error' is defined to allow capture of this module specific exceptions.
-"""
-
-import socket
-import select
-import base64
-import dispatcher
-from simplexml import ustr
-from client import PlugIn
-from protocol import *
-import os
-import errno
-
-DATA_RECEIVED='DATA RECEIVED'
-DATA_SENT='DATA SENT'
-
-def temp_failure_retry(func, *args, **kwargs):
- while True:
- try:
- return func(*args, **kwargs)
- except (os.error, IOError, select.error), ex:
- if hasattr(ex, 'errno'):
- errnum = ex.errno
- elif hasattr(ex, 'args') and ex.args is not None and len(ex.args) > 0:
- errnum = ex.args[0]
- else:
- errnum = -1
- if errnum == errno.EINTR:
- continue
- else:
- raise
-
-DBG_SOCKET = "socket"
-
-class TCPsocket(PlugIn):
- """ This class defines direct TCP connection method. """
- def __init__(self, server=None, use_srv=True):
- """ Cache connection point 'server'. 'server' is the tuple of (host, port)
- absolutely the same as standard tcp socket uses. """
- PlugIn.__init__(self)
- self.DBG_LINE = DBG_SOCKET
- self._exported_methods=[self.send,self.disconnect]
-
- self._server = server
-
- def plugin(self, owner):
- """ Fire up connection. Return non-empty string on success.
- Also registers self.disconnected method in the owner's dispatcher.
- Called internally. """
- if not self._server: self._server=(self._owner.Server,5222)
- if not self.connect(self._server): return
- self._owner.Connection=self
- self._owner.RegisterDisconnectHandler(self.disconnected)
- owner.debug_flags.append(DBG_SOCKET)
- return 'ok'
-
- def getHost(self):
- """ Return the 'host' value that is connection is [will be] made to."""
- return self._server[0]
- def getPort(self):
- """ Return the 'port' value that is connection is [will be] made to."""
- return self._server[1]
-
- def connect(self,server=None):
- """ Try to connect. Returns non-empty string on success. """
- try:
- if not server:
- server=self._server
- for ai in socket.getaddrinfo(server[0],server[1],socket.AF_UNSPEC,socket.SOCK_STREAM):
- try:
- self._sock=socket.socket(*ai[:3])
- self._sock.connect(ai[4])
- self._send=self._sock.sendall
- self._recv=self._sock.recv
- self.DEBUG("Successfully connected to remote host %s"%repr(server),'start')
- return 'ok'
- except Exception:
- continue
- except Exception:
- pass
-
- def plugout(self):
- """ Disconnect from the remote server and unregister self.disconnected method from
- the owner's dispatcher. """
- self._owner.DeregisterDisconnectHandler(self.disconnected)
- self.shutdown()
- del self._owner.Connection
-
- def receive(self):
- """ Reads all pending incoming data. Calls owner's disconnected() method if appropriate."""
- try:
- received = self._recv(1024000)
- except socket.error:
- received = ''
-
- while temp_failure_retry(select.select,[self._sock],[],[],0)[0]:
- try:
- add = self._recv(1024000)
- except socket.error:
- add=''
- received +=add
- if not add: break
-
- if len(received): # length of 0 means disconnect
- self.DEBUG(received,'got')
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
- else:
- self.DEBUG('Socket error while receiving data','error')
- self._owner.disconnected()
- return received
-
- def send(self,raw_data):
- """ Writes raw outgoing data. Blocks until done.
- If supplied data is unicode string, encodes it to utf-8 before send."""
- if isinstance(raw_data, unicode): raw_data = raw_data.encode('utf-8')
- elif not isinstance(raw_data, str): raw_data = ustr(raw_data).encode('utf-8')
- try:
- self._send(raw_data)
- # Avoid printing messages that are empty keepalive packets.
- if raw_data.strip():
- self.DEBUG(raw_data,'sent')
- self._owner.Dispatcher.Event('', DATA_SENT, raw_data)
- except Exception:
- self.DEBUG("Socket error while sending data",'error')
- self._owner.disconnected()
-
- def pending_data(self,timeout=0):
- """ Returns true if there is a data ready to be read. """
- return temp_failure_retry(select.select,[self._sock],[],[],timeout)[0]
-
- def disconnect(self):
- """ Closes the socket. """
- self.DEBUG("Closing socket",'stop')
- self._sock.close()
-
- def disconnected(self):
- """ Called when a Network Error or disconnection occurs.
- Designed to be overidden. """
- self.DEBUG("Socket operation failed",'error')
-
-DBG_CONNECT_PROXY='CONNECTproxy'
-class HTTPPROXYsocket(TCPsocket):
- """ HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
- redefines only connect method. Allows to use HTTP proxies like squid with
- (optionally) simple authentication (using login and password). """
- def __init__(self,proxy,server,use_srv=True):
- """ Caches proxy and target addresses.
- 'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
- and optional keys 'user' and 'password' to use for authentication.
- 'server' argument is a tuple of host and port - just like TCPsocket uses. """
- TCPsocket.__init__(self,server,use_srv)
- self.DBG_LINE=DBG_CONNECT_PROXY
- self._proxy=proxy
-
- def plugin(self, owner):
- """ Starts connection. Used interally. Returns non-empty string on success."""
- owner.debug_flags.append(DBG_CONNECT_PROXY)
- return TCPsocket.plugin(self,owner)
-
- def connect(self,dupe=None):
- """ Starts connection. Connects to proxy, supplies login and password to it
- (if were specified while creating instance). Instructs proxy to make
- connection to the target server. Returns non-empty sting on success. """
- if not TCPsocket.connect(self,(self._proxy['host'],self._proxy['port'])): return
- self.DEBUG("Proxy server contacted, performing authentification",'start')
- connector = ['CONNECT %s:%s HTTP/1.0'%self._server,
- 'Proxy-Connection: Keep-Alive',
- 'Pragma: no-cache',
- 'Host: %s:%s'%self._server,
- 'User-Agent: HTTPPROXYsocket/v0.1']
- if 'user' in self._proxy and 'password' in self._proxy:
- credentials = '%s:%s'%(self._proxy['user'],self._proxy['password'])
- credentials = base64.encodestring(credentials).strip()
- connector.append('Proxy-Authorization: Basic '+credentials)
- connector.append('\r\n')
- self.send('\r\n'.join(connector))
- try: reply = self.receive().replace('\r','')
- except IOError:
- self.DEBUG('Proxy suddenly disconnected','error')
- self._owner.disconnected()
- return
- try: proto,code,desc=reply.split('\n')[0].split(' ',2)
- except: raise Exception('Invalid proxy reply')
- if code!='200':
- self.DEBUG('Invalid proxy reply: %s %s %s'%(proto,code,desc),'error')
- self._owner.disconnected()
- return
- while reply.find('\n\n') == -1:
- try: reply += self.receive().replace('\r','')
- except IOError:
- self.DEBUG('Proxy suddenly disconnected','error')
- self._owner.disconnected()
- return
- self.DEBUG("Authentification successfull. Jabber server contacted.",'ok')
- return 'ok'
-
- def DEBUG(self,text,severity):
- """Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy"."""
- return self._owner.DEBUG(DBG_CONNECT_PROXY,text,severity)
-
-class TLS(PlugIn):
- """ TLS connection used to encrypts already estabilished tcp connection."""
- def PlugIn(self,owner,now=0):
- """ If the 'now' argument is true then starts using encryption immidiatedly.
- If 'now' in false then starts encryption as soon as TLS feature is
- declared by the server (if it were already declared - it is ok).
- """
- if 'TLS' in owner.__dict__: return # Already enabled.
- PlugIn.PlugIn(self,owner)
- self.DBG_LINE='TLS'
- if now: return self._startSSL()
- if self._owner.Dispatcher.Stream.features:
- try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
- except NodeProcessed: pass
- else: self._owner.RegisterHandlerOnce('features',self.FeaturesHandler,xmlns=NS_STREAMS)
- self.starttls=None
-
- def plugout(self,now=0):
- """ Unregisters TLS handler's from owner's dispatcher. Take note that encription
- can not be stopped once started. You can only break the connection and start over."""
- self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
-# self._owner.UnregisterHandlerOnce('proceed',self.StartTLSHandler,xmlns=NS_TLS)
-# self._owner.UnregisterHandlerOnce('failure',self.StartTLSHandler,xmlns=NS_TLS)
-
- def FeaturesHandler(self, conn, feats):
- """ Used to analyse server tag for TLS support.
- If TLS is supported starts the encryption negotiation. Used internally"""
- if not feats.getTag('starttls',namespace=NS_TLS):
- self.DEBUG("TLS unsupported by remote server.",'warn')
- return
- self.DEBUG("TLS supported by remote server. Requesting TLS start.",'ok')
- self._owner.RegisterHandlerOnce('proceed',self.StartTLSHandler,xmlns=NS_TLS)
- self._owner.RegisterHandlerOnce('failure',self.StartTLSHandler,xmlns=NS_TLS)
- self._owner.Connection.send(''%NS_TLS)
- raise NodeProcessed
-
- def pending_data(self,timeout=0):
- """ Returns true if there possible is a data ready to be read. """
- return self._tcpsock._seen_data or select.select([self._tcpsock._sock],[],[],timeout)[0]
-
- def _startSSL(self):
- """ Immidiatedly switch socket to TLS mode. Used internally."""
- # Here we should switch pending_data to hint mode.
- tcpsock=self._owner.Connection
- tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
- tcpsock._sslIssuer = tcpsock._sslObj.issuer()
- tcpsock._sslServer = tcpsock._sslObj.server()
- tcpsock._recv = tcpsock._sslObj.read
- tcpsock._send = tcpsock._sslObj.write
- self.starttls='success'
-
- def StartTLSHandler(self, conn, starttls):
- """ Handle server reply if TLS is allowed to process. Behaves accordingly.
- Used internally."""
- if starttls.getNamespace()!=NS_TLS: return
- self.starttls=starttls.getName()
- if self.starttls=='failure':
- self.DEBUG("Got starttls response: "+self.starttls,'error')
- return
- self.DEBUG("Got starttls proceed response. Switching to TLS/SSL...",'ok')
- self._startSSL()
- self._owner.Dispatcher.PlugOut()
- dispatcher.Dispatcher().PlugIn(self._owner)
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py
index 500e55a97..d5750fd7b 100644
--- a/src/common/xmpp/transports_nb.py
+++ b/src/common/xmpp/transports_nb.py
@@ -3,6 +3,7 @@
##
## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
## modified by Dimitur Kirov
+## modified by Tomas Karasek
##
## 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
@@ -14,260 +15,106 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
-import socket
-import base64
-import dispatcher_nb
-import struct
from simplexml import ustr
from client import PlugIn
from idlequeue import IdleObject
from protocol import *
-from transports import *
+import proxy_connectors
+import tls_nb
+import socket
import sys
import os
import errno
import time
-
import traceback
+import base64
import logging
log = logging.getLogger('gajim.c.x.transports_nb')
-import common.gajim
+def urisplit(uri):
+ '''
+ Function for splitting URI string to tuple (protocol, host, path).
+ e.g. urisplit('http://httpcm.jabber.org/webclient') returns
+ ('http', 'httpcm.jabber.org', '/webclient')
+ '''
+ import re
+ regex = '(([^:/]+)(://))?([^/]*)(/?.*)'
+ grouped = re.match(regex, uri).groups()
+ proto, host, path = grouped[1], grouped[3], grouped[4]
+ return proto, host, path
-USE_PYOPENSSL = False
-
-DBG_NONBLOCKINGTLS= 'NonBlockingTLS'
-try:
- #raise ImportError("Manually disabled PyOpenSSL")
- import OpenSSL.SSL
- import OpenSSL.crypto
- USE_PYOPENSSL = True
- log.info("PyOpenSSL loaded")
-except ImportError:
- log.debug("Import of PyOpenSSL failed:", exc_info=True)
-
- # FIXME: Remove these prints before release, replace with a warning dialog.
- print >> sys.stderr, "=" * 79
- print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
- print >> sys.stderr, "=" * 79
+def get_proxy_data_from_dict(proxy):
+ tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None
+ type = proxy['type']
+ if type == 'bosh' and not proxy['bosh_useproxy']:
+ # with BOSH not over proxy we have to parse the hostname from BOSH URI
+ tcp_host, tcp_port = urisplit(proxy['bosh_uri'])[1], proxy['bosh_port']
+ else:
+ # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy
+ # machine
+ tcp_host, tcp_port = proxy['host'], proxy['port']
+ if proxy['useauth']:
+ proxy_user, proxy_pass = proxy['user'], proxy['pass']
+ return tcp_host, tcp_port, proxy_user, proxy_pass
# timeout to connect to the server socket, it doesn't include auth
CONNECT_TIMEOUT_SECONDS = 30
# how long to wait for a disconnect to complete
-DISCONNECT_TIMEOUT_SECONDS = 10
+DISCONNECT_TIMEOUT_SECONDS = 5
# size of the buffer which reads data from server
# if lower, more stanzas will be fragmented and processed twice
RECV_BUFSIZE = 32768 # 2x maximum size of ssl packet, should be plenty
#RECV_BUFSIZE = 16 # FIXME: (#2634) gajim breaks with this setting: it's inefficient but should work.
-def torf(cond, tv, fv):
- if cond: return tv
- return fv
+DATA_RECEIVED='DATA RECEIVED'
+DATA_SENT='DATA SENT'
-def gattr(obj, attr, default=None):
- try:
- return getattr(obj, attr)
- except Exception:
- return default
+DISCONNECTED = 'DISCONNECTED'
+DISCONNECTING = 'DISCONNECTING'
+CONNECTING = 'CONNECTING'
+PROXY_CONNECTING = 'PROXY_CONNECTING'
+CONNECTED = 'CONNECTED'
+STATES = [DISCONNECTED, CONNECTING, PROXY_CONNECTING, CONNECTED, DISCONNECTING]
+# Transports have different arguments in constructor and same connect() method.
-class SSLWrapper:
- class Error(IOError):
- def __init__(self, sock=None, exc=None, errno=None, strerror=None, peer=None):
- self.parent = IOError
-
- errno = errno or gattr(exc, 'errno')
- strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
- if not isinstance(strerror, basestring): strerror = repr(strerror)
-
- self.sock = sock
- self.exc = exc
- self.peer = peer
- self.exc_name = None
- self.exc_args = None
- self.exc_str = None
- self.exc_repr = None
-
- if self.exc is not None:
- self.exc_name = str(self.exc.__class__)
- self.exc_args = gattr(self.exc, 'args')
- self.exc_str = str(self.exc)
- self.exc_repr = repr(self.exc)
- if not errno:
- try:
- if isinstance(exc, OpenSSL.SSL.SysCallError):
- if self.exc_args[0] > 0:
- errno = self.exc_args[0]
- strerror = self.exc_args[1]
- except Exception:
- pass
-
- self.parent.__init__(self, errno, strerror)
-
- if self.peer is None and sock is not None:
- try:
- ppeer = self.sock.getpeername()
- if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
- and isinstance(ppeer[1], int):
- self.peer = ppeer
- except Exception:
- pass
-
- def __str__(self):
- s = str(self.__class__)
- if self.peer: s += " for %s:%d" % self.peer
- if self.errno is not None: s += ": [Errno: %d]" % self.errno
- if self.strerror: s += " (%s)" % self.strerror
- if self.exc_name:
- s += ", Caused by %s" % self.exc_name
- if self.exc_str:
- if self.strerror: s += "(%s)" % self.exc_str
- else: s += "(%s)" % str(self.exc_args)
- return s
-
- def __init__(self, sslobj, sock=None):
- self.sslobj = sslobj
- self.sock = sock
- log.debug("%s.__init__ called with %s", self.__class__, sslobj)
-
- def recv(self, data, flags=None):
- """ Receive wrapper for SSL object
-
- We can return None out of this function to signal that no data is
- available right now. Better than an exception, which differs
- depending on which SSL lib we're using. Unfortunately returning ''
- can indicate that the socket has been closed, so to be sure, we avoid
- this by returning None. """
-
- raise NotImplementedError()
-
- def send(self, data, flags=None, now = False):
- raise NotImplementedError()
-
-class PyOpenSSLWrapper(SSLWrapper):
- '''Wrapper class for PyOpenSSL's recv() and send() methods'''
-
- def __init__(self, *args):
- self.parent = SSLWrapper
- self.parent.__init__(self, *args)
-
- def is_numtoolarge(self, e):
- t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large')
- return isinstance(e.args, (list, tuple)) and len(e.args) == 1 and \
- isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and \
- e.args[0][0] == e.args[0][1] == t
-
- def recv(self, bufsize, flags=None):
- retval = None
- try:
- if flags is None: retval = self.sslobj.recv(bufsize)
- else: retval = self.sslobj.recv(bufsize, flags)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
- pass
- # log.debug("Recv: " + repr(e))
- except OpenSSL.SSL.SysCallError, e:
- log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
- #traceback.print_exc()
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- except OpenSSL.SSL.Error, e:
- if self.is_numtoolarge(e):
- # warn, but ignore this exception
- log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
- else:
- log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
- #traceback.print_exc()
- #print "Current Stack:"
- #traceback.print_stack()
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return retval
-
- def send(self, data, flags=None, now = False):
- try:
- if flags is None: return self.sslobj.send(data)
- else: return self.sslobj.send(data, flags)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
- #log.debug("Send: " + repr(e))
- time.sleep(0.1) # prevent 100% CPU usage
- except OpenSSL.SSL.SysCallError, e:
- log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True)
- #traceback.print_exc()
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- except OpenSSL.SSL.Error, e:
- if self.is_numtoolarge(e):
- # warn, but ignore this exception
- log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
- else:
- log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True)
- #traceback.print_exc()
- #print "Current Stack:"
- #traceback.print_stack()
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return 0
-
-class StdlibSSLWrapper(SSLWrapper):
- '''Wrapper class for Python's socket.ssl read() and write() methods'''
-
- def __init__(self, *args):
- self.parent = SSLWrapper
- self.parent.__init__(self, *args)
-
- def recv(self, bufsize, flags=None):
- # we simply ignore flags since ssl object doesn't support it
- try:
- return self.sslobj.read(bufsize)
- except socket.sslerror, e:
- #log.debug("Recv: Caught socket.sslerror:", exc_info=True)
- #traceback.print_exc()
- if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return None
-
- def send(self, data, flags=None, now = False):
- # we simply ignore flags since ssl object doesn't support it
- try:
- return self.sslobj.write(data)
- except socket.sslerror, e:
- #log.debug("Send: Caught socket.sslerror:", exc_info=True)
- #traceback.print_exc()
- if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return 0
-
-class NonBlockingTcp(PlugIn, IdleObject):
- ''' This class can be used instead of transports.Tcp in threadless implementations '''
- def __init__(self, on_connect = None, on_connect_failure = None, server=None, use_srv = True):
- ''' Cache connection point 'server'. 'server' is the tuple of (host, port)
- absolutely the same as standard tcp socket uses.
- on_connect - called when we connect to the socket
- on_connect_failure - called if there was error connecting to socket
- '''
- IdleObject.__init__(self)
+class NonBlockingTransport(PlugIn):
+ '''
+ Abstract class representing a trasport - object responsible for connecting to
+ XMPP server and putting stanzas on wire in desired form.
+ '''
+ def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs):
+ '''
+ Each trasport class can have different constructor but it has to have at
+ least all the arguments of NonBlockingTransport constructor.
+
+ :param raise_event: callback for monitoring of sent and received data
+ :param on_disconnect: callback called on disconnection during runtime
+ :param idlequeue: processing idlequeue
+ :param estabilish_tls: boolean whether to estabilish TLS connection after TCP
+ connection is done
+ :param certs: tuple of (cacerts, mycerts) see tls_nb.NonBlockingTLS
+ constructor for more details
+ '''
PlugIn.__init__(self)
- self.DBG_LINE='socket'
- self._exported_methods=[self.send, self.disconnect, self.onreceive, self.set_send_timeout,
- self.start_disconnect, self.set_timeout, self.remove_timeout]
- self._server = server
- self._hostfqdn = server[0]
- self.on_connect = on_connect
- self.on_connect_failure = on_connect_failure
+ self.raise_event = raise_event
+ self.on_disconnect = on_disconnect
+ self.on_connect = None
+ self.on_connect_failure = None
+ self.idlequeue = idlequeue
self.on_receive = None
- self.on_disconnect = None
- self.printed_error = False
-
- # 0 - not connected
- # 1 - connected
- # -1 - about to disconnect (when we wait for final events to complete)
- # -2 - disconnected
- self.state = 0
-
- # queue with messages to be send
- self.sendqueue = []
-
- # bytes remained from the last send message
- self.sendbuff = ''
+ self.server = None
+ self.port = None
+ self.set_state(DISCONNECTED)
+ self.estabilish_tls = estabilish_tls
+ self.certs = certs
+ # type of used ssl lib (if any) will be assigned to this member var
+ self.ssl_lib = None
+ self._exported_methods=[self.onreceive, self.set_send_timeout,
+ self.set_timeout, self.remove_timeout, self.start_disconnect]
# time to wait for SOME stanza to come and then send keepalive
self.sendtimeout = 0
@@ -275,375 +122,99 @@ class NonBlockingTcp(PlugIn, IdleObject):
# in case we want to something different than sending keepalives
self.on_timeout = None
- # writable, readable - keep state of the last pluged flags
- # This prevents replug of same object with the same flags
- self.writable = True
- self.readable = False
- self.ais = None
-
def plugin(self, owner):
- ''' Fire up connection. Return non-empty string on success.
- Also registers self.disconnected method in the owner's dispatcher.
- Called internally. '''
- self.idlequeue = owner.idlequeue
- self.printed_error = False
- if not self._server:
- self._server=(self._owner.Server,5222)
- if self.connect(self._server) is False:
- return False
- return True
-
- def read_timeout(self):
- if self.state == 0:
- self.idlequeue.unplug_idle(self.fd)
- if self.on_connect_failure:
- self.on_connect_failure()
- else:
- if self.on_timeout:
- self.on_timeout()
- self.renew_send_timeout()
-
- def connect(self,server=None, proxy = None, secure = None):
- ''' Try to establish connection. '''
- if not server:
- server=self._server
- else:
- self._server = server
- self._hostfqdn = self._server[0]
- self.printed_error = False
- self.state = 0
- try:
- if len(server) == 2 and type(server[0]) in (str, unicode) and not \
- self.ais:
- # FIXME: blocks here
- self.ais = socket.getaddrinfo(server[0],server[1],socket.AF_UNSPEC,socket.SOCK_STREAM)
- log.info('Found IPs: %s', self.ais)
- else:
- self.ais = (server,)
- self.connect_to_next_ip()
- return
- except socket.gaierror, e:
- log.info('Lookup failure for %s: %s[%s]', self.getName(), e[1], repr(e[0]), exc_info=True)
- except:
- log.error('Exception trying to connect to %s:', self.getName(), exc_info=True)
-
- if self.on_connect_failure:
- self.on_connect_failure()
-
- def _plug_idle(self):
- readable = self.state != 0
- if self.sendqueue or self.sendbuff:
- writable = True
- else:
- writable = False
- if self.writable != writable or self.readable != readable:
- self.idlequeue.plug_idle(self, writable, readable)
-
- def pollout(self):
- if self.state == 0:
- self._do_connect()
- return
- self._do_send()
+ owner.Connection=self
def plugout(self):
- ''' Disconnect from the remote server and unregister self.disconnected method from
- the owner's dispatcher. '''
- self.disconnect()
self._owner.Connection = None
self._owner = None
+ self.disconnect(do_callback=False)
- def pollin(self):
- self._do_receive()
+ def connect(self, conn_5tuple, on_connect, on_connect_failure):
+ '''
+ Creates and connects transport to server and port defined in conn_5tupe which
+ should be item from list returned from getaddrinfo.
+ :param conn_5tuple: 5-tuple returned from getaddrinfo
+ :param on_connect: callback called on successful connect to the server
+ :param on_connect_failure: callback called on failure when connecting
+ '''
+ self.on_connect = on_connect
+ self.on_connect_failure = on_connect_failure
+ self.server, self.port = conn_5tuple[4][:2]
+ self.conn_5tuple = conn_5tuple
- def pollend(self, retry=False):
- if not self.printed_error:
- self.printed_error = True
- try:
- self._do_receive(errors_only=True)
- except Exception:
- log.error("pollend: Got exception from _do_receive:", exc_info=True)
- conn_failure_cb = self.on_connect_failure
- self.disconnect()
- if conn_failure_cb:
- conn_failure_cb(retry)
+ def set_state(self, newstate):
+ assert(newstate in STATES)
+ self.state = newstate
- def disconnect(self):
- if self.state == -2: # already disconnected
- return
- self.state = -2
- self.sendqueue = None
- self.remove_timeout()
- try:
- self._owner.disconnected()
- except Exception:
- pass
- self.idlequeue.unplug_idle(self.fd)
- sock = getattr(self, '_sock', None)
- if sock:
- try:
- sock.shutdown(socket.SHUT_RDWR)
- except socket.error, e:
- if e[0] != errno.ENOTCONN:
- log.error("Error shutting down socket for %s:", self.getName(), exc_info=True)
- try:
- sock.close()
- except Exception:
- log.error("Error closing socket for %s:", self.getName(), exc_info=True)
- # socket descriptor cannot be (un)plugged anymore
- self.fd = -1
- if self.on_disconnect:
+ def get_state(self):
+ return self.state
+
+ def _on_connect(self):
+ ''' preceeds call of on_connect callback '''
+ # data is reference to socket wrapper instance. We don't need it in client
+ # because
+ self.set_state(CONNECTED)
+ self.on_connect()
+
+ def _on_connect_failure(self,err_message):
+ ''' preceeds call of on_connect_failure callback '''
+ # In case of error while connecting we need to disconnect transport
+ # but we don't want to call DisconnectHandlers from client,
+ # thus the do_callback=False
+ self.disconnect(do_callback=False)
+ self.on_connect_failure(err_message=err_message)
+
+ def send(self, raw_data, now=False):
+ if self.get_state() == DISCONNECTED:
+ log.error('Unable to send %s \n because state is %s.' %
+ (raw_data, self.get_state()))
+
+ def disconnect(self, do_callback=True):
+ self.set_state(DISCONNECTED)
+ if do_callback:
+ # invoke callback given in __init__
self.on_disconnect()
- self.on_connect_failure = None
-
- def end_disconnect(self):
- ''' force disconnect only if we are still trying to disconnect '''
- if self.state == -1:
- self.disconnect()
-
- def start_disconnect(self, to_send, on_disconnect):
- self.on_disconnect = on_disconnect
-
- # flush the sendqueue
- while self.sendqueue:
- self._do_send()
-
- self.sendqueue = []
- self.send(to_send)
- self.send('')
- self.state = -1 # about to disconnect
- self.idlequeue.set_alarm(self.end_disconnect, DISCONNECT_TIMEOUT_SECONDS)
-
- def set_timeout(self, timeout):
- if self.state >= 0 and self.fd > 0:
- self.idlequeue.set_read_timeout(self.fd, timeout)
-
- def remove_timeout(self):
- if self.fd:
- self.idlequeue.remove_timeout(self.fd)
def onreceive(self, recv_handler):
- ''' Sets the on_receive callback. Do not confuse it with
- on_receive() method, which is the callback itself.
-
- If recv_handler==None, it tries to set that callback assuming that
- our owner also has a Dispatcher object plugged in, to its
- ProcessNonBlocking method.'''
+ '''
+ Sets the on_receive callback. Do not confuse it with on_receive() method,
+ which is the callback itself.
+ onreceive(None) sets callback to Dispatcher.ProcessNonBlocking which is the
+ default one that will decide what to do with received stanza based on its
+ tag name and namespace.
+ '''
if not recv_handler:
if hasattr(self._owner, 'Dispatcher'):
self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
else:
self.on_receive = None
return
- _tmp = self.on_receive
- # make sure this cb is not overriden by recursive calls
- if not recv_handler(None) and _tmp == self.on_receive:
- self.on_receive = recv_handler
+ self.on_receive = recv_handler
- def _do_receive(self, errors_only=False):
- ''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.'''
- ERR_DISCONN = -2 # Misc error signifying that we got disconnected
- ERR_OTHER = -1 # Other error
- received = None
- errnum = 0
- errtxt = 'No Error Set'
- try:
- # get as many bites, as possible, but not more than RECV_BUFSIZE
- received = self._recv(RECV_BUFSIZE)
- except (socket.error, socket.herror, socket.gaierror), e:
- log.debug("_do_receive: got %s:", e.__class__, exc_info=True)
- #traceback.print_exc()
- #print "Current Stack:"
- #traceback.print_stack()
- errnum = e[0]
- errtxt = str(errnum) + ':' + e[1]
- except socket.sslerror, e:
- log.error("_do_receive: got unknown %s:", e.__class__, exc_info=True)
- #traceback.print_exc()
- #print "Current Stack:"
- #traceback.print_stack()
- errnum = ERR_OTHER
- errtxt = repr("socket.sslerror: " + e.args)
- except SSLWrapper.Error, e:
- log.debug("Caught: %s", str(e))
- errnum = gattr(e, 'errno', ERR_OTHER)
- if not errnum: errnum = ERR_OTHER # unset, but we must put a status
- errtxt = gattr(e, 'strerror') or repr(e.args)
+ def tcp_connecting_started(self):
+ self.set_state(CONNECTING)
- if received == '':
- errnum = ERR_DISCONN
- errtxt = "Connection closed unexpectedly"
-
- if errnum in (ERR_DISCONN, errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN):
- log.error("Connection to %s lost: %s [%d]", self.getName(), errtxt, errnum)
- self.printed_error = True
- if not errors_only:
- self.pollend(retry=(errnum in (ERR_DISCONN, errno.ECONNRESET)))
- # don't process result, because it will raise an error
- return
-
- if received is None:
- if self.state == 0 and errnum == errno.ECONNREFUSED:
- # We tried to connect to a port that did't listen.
- log.error("Connection to %s refused: %s [%d]", self.getName(), errtxt, errnum)
- self.pollend(retry=True)
- return
- elif errnum != 0:
- self.DEBUG(errtxt, 'error')
- log.error("Connection to %s lost: %s [%d]", self.getName(), errtxt, errnum)
- self._owner.disconnected()
- self.printed_error = True
- if not errors_only and self.state >= 0:
- self.pollend(retry=True)
- return
- received = ''
-
- if errors_only or self.state < 0:
- return
-
- # we have received some bites, stop the timeout!
+ def read_timeout(self):
+ ''' called when there's no response from server in defined timeout '''
+ if self.on_timeout:
+ self.on_timeout()
self.renew_send_timeout()
- if self.on_receive:
- if received.strip():
- self.DEBUG(received, 'got')
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
- self.on_receive(received)
+
+ def renew_send_timeout(self):
+ if self.on_timeout and self.sendtimeout > 0:
+ self.set_timeout(self.sendtimeout)
else:
- # This should never happed, so we need the debug
- self.DEBUG('Unhandled data received: %s' % received,'got')
- self.disconnect()
- if self.on_connect_failure:
- self.on_connect_failure()
- return True
+ self.remove_timeout()
- def _do_send(self):
- if not self.sendbuff:
- if not self.sendqueue:
- return None # nothing to send
- self.sendbuff = self.sendqueue.pop(0)
- self.sent_data = self.sendbuff
- try:
- send_count = self._send(self.sendbuff)
- if send_count:
- self.sendbuff = self.sendbuff[send_count:]
- if not self.sendbuff and not self.sendqueue:
- if self.state < 0:
- self.idlequeue.unplug_idle(self.fd)
- self._on_send()
- self.disconnect()
- return
- # we are not waiting for write
- self._plug_idle()
- self._on_send()
- except socket.error, e:
- if e[0] == socket.SSL_ERROR_WANT_WRITE:
- return True
- log.error("_do_send:", exc_info=True)
- #traceback.print_exc()
- if self.state < 0:
- self.disconnect()
- return
- if self._on_send_failure:
- self._on_send_failure()
- return
- return True
+ def set_timeout(self, timeout):
+ self.idlequeue.set_read_timeout(self.fd, timeout)
- def connect_to_next_ip(self):
- if self.state != 0:
- return
- if len(self.ais) == 0:
- if self.on_connect_failure:
- self.on_connect_failure()
- return
- ai = self.ais.pop(0)
- log.info('Trying to connect to %s:%s', ai[4][0], ai[4][1])
- try:
- self._sock = socket.socket(*ai[:3])
- self._sock.setblocking(False)
- self._server=ai[4]
- except socket.error, e:
- errnum, errstr = e
+ def get_fd(self):
+ pass
- # Ignore "Socket already connected".
- # FIXME: This happens when we switch an already
- # connected socket to SSL (STARTTLS). Instead of
- # ignoring the error, the socket should only be
- # connected to once. See #2846 and #3396.
- workaround = (errno.EALREADY, 10056, 56)
-
- # 10035 - winsock equivalent of EINPROGRESS
- if errnum not in (errno.EINPROGRESS, 10035) + workaround:
- log.error('Could not connect to %s: %s [%s]', ai[4][0], errnum,
- errstr, exc_info=True)
- #traceback.print_exc()
- self.connect_to_next_ip()
- return
- self.fd = self._sock.fileno()
- self.idlequeue.plug_idle(self, True, False)
- self._send = self._sock.send
- self._recv = self._sock.recv
- self.set_timeout(CONNECT_TIMEOUT_SECONDS)
- self._do_connect()
-
- def _do_connect(self):
- errnum = 0
- if self.state != 0:
- return
-
- try:
- self._sock.connect(self._server)
- except Exception, ee:
- (errnum, errstr) = ee
- # in progress, or would block
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- return
- # 10056 - already connected, only on win32
- # code 'WS*' is not available on GNU, so we use its numeric value
- elif errnum not in (0, 10056, errno.EISCONN):
- log.error('Could not connect to %s: %s [%s]', self._server[0], errnum,
- errstr)
- self.connect_to_next_ip()
- return
- self.remove_timeout()
- self._owner.Connection=self
- self.state = 1
-
- self._plug_idle()
- if self.on_connect:
- self.on_connect()
- self.on_connect = None
-
- def send(self, raw_data, now = False):
- '''Append raw_data to the queue of messages to be send.
- If supplied data is unicode string, encode it to utf-8.
- '''
-
- if self.state <= 0:
- return
- r = raw_data
- if isinstance(r, unicode):
- r = r.encode('utf-8')
- elif not isinstance(r, str):
- r = ustr(r).encode('utf-8')
- if now:
- self.sendqueue.insert(0, r)
- self._do_send()
- else:
- self.sendqueue.append(r)
-
- self._plug_idle()
-
- def _on_send(self):
- if self.sent_data and self.sent_data.strip():
- self.DEBUG(self.sent_data,'sent')
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
- self.sent_data = None
-
- def _on_send_failure(self):
- self.DEBUG("Socket error while sending data",'error')
- self._owner.disconnected()
- self.sent_data = None
+ def remove_timeout(self):
+ self.idlequeue.remove_timeout(self.fd)
def set_send_timeout(self, timeout, on_timeout):
self.sendtimeout = timeout
@@ -652,486 +223,462 @@ class NonBlockingTcp(PlugIn, IdleObject):
else:
self.on_timeout = None
- def renew_send_timeout(self):
- if self.on_timeout and self.sendtimeout > 0:
- self.set_timeout(self.sendtimeout)
- else:
- self.remove_timeout()
+ def start_disconnect(self):
+ self.set_state(DISCONNECTING)
- def getHost(self):
- ''' Return the 'host' value that is connection is [will be] made to.'''
- return self._server[0]
- def getName(self):
- ''' Return the server's name, or 'getHost()' if not available.'''
- retval = None
- try:
- retval = gattr(self._owner, 'name')
- except Exception:
- pass
- if retval: return retval
- return self.getHost()
-
- def getPort(self):
- ''' Return the 'port' value that is connection is [will be] made to.'''
- return self._server[1]
-
-DBG_NONBLOCKINGTLS = "NonBlockingTLS"
-
-class NonBlockingTLS(PlugIn):
- ''' TLS connection used to encrypts already estabilished tcp connection.'''
-
- # from ssl.h (partial extract)
- ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
- "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
- "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08,
- "SSL_CB_ALERT": 0x4000,
- "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
-
- def PlugIn(self, owner, now=0, on_tls_start = None):
- ''' If the 'now' argument is true then starts using encryption immidiatedly.
- If 'now' in false then starts encryption as soon as TLS feature is
- declared by the server (if it were already declared - it is ok).
+class NonBlockingTCP(NonBlockingTransport, IdleObject):
+ '''
+ Non-blocking TCP socket wrapper. It is used for simple XMPP connection. Can be
+ connected via proxy and can estabilish TLS connection.
+ '''
+ def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls,
+ certs, proxy_dict=None):
'''
- if 'NonBlockingTLS' in owner.__dict__:
- return # Already enabled.
- PlugIn.PlugIn(self, owner)
- self.DBG_LINE = DBG_NONBLOCKINGTLS
- owner.debug_flags.append(DBG_NONBLOCKINGTLS)
- self.on_tls_start = on_tls_start
+ :param proxy_dict: dictionary with proxy data as loaded from config file
+ '''
+ NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
+ estabilish_tls, certs)
+
+ # queue with messages to be send
+ self.sendqueue = []
+
+ # bytes remained from the last send message
+ self.sendbuff = ''
+
+ self.proxy_dict = proxy_dict
+ self.on_remote_disconnect = self.disconnect
+
+ def start_disconnect(self):
+ NonBlockingTransport.start_disconnect(self)
+ self.send('', now=True)
+ self.disconnect()
+
+ def connect(self, conn_5tuple, on_connect, on_connect_failure):
+ NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
+ log.info('NonBlockingTCP Connect :: About to connect to %s:%s' % (self.server, self.port))
+
+ try:
+ self._sock = socket.socket(*conn_5tuple[:3])
+ except socket.error, (errnum, errstr):
+ self._on_connect_failure('NonBlockingTCP Connect: Error while creating socket:\
+ %s %s' % (errnum, errstr))
+ return
+
+ self._send = self._sock.send
+ self._recv = self._sock.recv
+ self.fd = self._sock.fileno()
+
+ # we want to be notified when send is possible to connected socket because
+ # it means the TCP connection is estabilished
+ self._plug_idle(writable=True, readable=False)
+ self.peerhost = None
+
+ # variable for errno symbol that will be found from exception raised from connect()
+ errnum = 0
+
+ # set timeout for TCP connecting - if nonblocking connect() fails, pollend
+ # is called. If if succeeds pollout is called.
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS)
+
+ try:
+ self._sock.setblocking(False)
+ self._sock.connect((self.server,self.port))
+ except Exception, (errnum, errstr):
+ pass
+
+ if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
+ # connecting in progress
+ log.info('After NB connect() of %s. "%s" raised => CONNECTING' % (id(self),errstr))
+ self.tcp_connecting_started()
+ return
+
+ # if there was some other exception, call failure callback and unplug transport
+ # which will also remove read_timeouts for descriptor
+ self._on_connect_failure('Exception while connecting to %s:%s - %s %s' %
+ (self.server, self.port, errnum, errstr))
+
+ def _connect_to_proxy(self):
+ self.set_state(PROXY_CONNECTING)
+ if self.proxy_dict['type'] == 'socks5':
+ proxyclass = proxy_connectors.SOCKS5Connector
+ elif self.proxy_dict['type'] == 'http' :
+ proxyclass = proxy_connectors.HTTPCONNECTConnector
+ proxyclass(
+ send_method = self.send,
+ onreceive = self.onreceive,
+ old_on_receive = self.on_receive,
+ on_success = self._on_connect,
+ on_failure = self._on_connect_failure,
+ xmpp_server = self.proxy_dict['xmpp_server'],
+ proxy_creds = self.proxy_dict['credentials']
+ )
+
+ def _on_connect(self):
+ '''
+ Preceeds invoking of on_connect callback. TCP connection is already
+ estabilished by this time.
+ '''
+ if self.estabilish_tls:
+ self.tls_init(
+ on_succ = lambda: NonBlockingTransport._on_connect(self),
+ on_fail = lambda: self._on_connect_failure('error while estabilishing TLS'))
+ else:
+ NonBlockingTransport._on_connect(self)
+
+
+ def tls_init(self, on_succ, on_fail):
+ '''
+ Estabilishes a TLS/SSL on TCP connection by plugging a NonBlockingTLS module
+ '''
+ cacerts, mycerts = self.certs
+ result = tls_nb.NonBlockingTLS(cacerts, mycerts).PlugIn(self)
+ if result:
+ on_succ()
+ else:
+ on_fail()
+
+ def pollin(self):
+ '''called when receive on plugged socket is possible '''
+ log.info('pollin called, state == %s' % self.get_state())
+ self._do_receive()
+
+ def pollout(self):
+ '''called when send to plugged socket is possible'''
+ log.info('pollout called, state == %s' % self.get_state())
+
+ if self.get_state()==CONNECTING:
+ log.info('%s socket wrapper connected' % id(self))
+ self.idlequeue.remove_timeout(self.fd)
+ self._plug_idle(writable=False, readable=False)
+ self.peerhost = self._sock.getsockname()
+ if self.proxy_dict: self._connect_to_proxy()
+ else: self._on_connect()
+ return
+ self._do_send()
+
+ def pollend(self):
+ '''called on error on TCP connection'''
+ log.info('pollend called, state == %s' % self.get_state())
+
+ if self.get_state()==CONNECTING:
+ self._on_connect_failure('Error during connect to %s:%s' %
+ (self.server, self.port))
+ else:
+ self.disconnect()
+
+ def disconnect(self, do_callback=True):
+ if self.get_state() == DISCONNECTED:
+ return
+ self.set_state(DISCONNECTED)
+ self.idlequeue.unplug_idle(self.fd)
+ if 'NonBlockingTLS' in self.__dict__: self.NonBlockingTLS.PlugOut()
+ try:
+ self._sock.shutdown(socket.SHUT_RDWR)
+ self._sock.close()
+ except socket.error, (errnum, errstr):
+ log.error('Error while disconnecting socket: %s' % errstr)
+ self.fd = -1
+ NonBlockingTransport.disconnect(self, do_callback)
+
+ def read_timeout(self):
+ ''' method called when timeout passed '''
+ log.info('read_timeout called, state == %s' % self.get_state())
+ if self.get_state()==CONNECTING:
+ # if read_timeout is called during connecting, connect() didn't end yet
+ # thus we have to call the tcp failure callback
+ self._on_connect_failure('Error during connect to %s:%s' %
+ (self.server, self.port))
+ else:
+ NonBlockingTransport.read_timeout(self)
+
+ def set_timeout(self, timeout):
+ if self.get_state() != DISCONNECTED and self.fd != -1:
+ NonBlockingTransport.set_timeout(self, timeout)
+ else:
+ log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd))
+
+ def remove_timeout(self):
+ if self.fd:
+ NonBlockingTransport.remove_timeout(self)
+ else:
+ log.warn('remove_timeout: no self.fd state is %s' % self.get_state())
+
+ def send(self, raw_data, now=False):
+ '''Append raw_data to the queue of messages to be send.
+ If supplied data is unicode string, encode it to utf-8.
+ '''
+ NonBlockingTransport.send(self, raw_data, now)
+
+ r = self.encode_stanza(raw_data)
+
if now:
- try:
- res = self._startSSL()
- except Exception:
- log.error("PlugIn: while trying _startSSL():", exc_info=True)
- #traceback.print_exc()
- self._owner.socket.pollend()
- return
- self.tls_start()
- return res
- if self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
+ self.sendqueue.insert(0, r)
+ self._do_send()
else:
- self._owner.RegisterHandlerOnce('features',self.FeaturesHandler, xmlns=NS_STREAMS)
- self.starttls = None
+ self.sendqueue.append(r)
- def plugout(self,now=0):
- ''' Unregisters TLS handler's from owner's dispatcher. Take note that encription
- can not be stopped once started. You can only break the connection and start over.'''
- # if dispatcher is not plugged we cannot (un)register handlers
- if 'Dispatcher' in self._owner.__dict__:
- self._owner.UnregisterHandler('features', self.FeaturesHandler,xmlns=NS_STREAMS)
- self._owner.Dispatcher.PlugOut()
- self._owner = None
+ self._plug_idle(writable=True, readable=True)
- def tls_start(self):
- if self.on_tls_start:
- self.on_tls_start()
- self.on_tls_start = None
+ def encode_stanza(self, stanza):
+ if isinstance(stanza, unicode):
+ stanza = stanza.encode('utf-8')
+ elif not isinstance(stanza, str):
+ stanza = ustr(stanza).encode('utf-8')
+ return stanza
- def FeaturesHandler(self, conn, feats):
- ''' Used to analyse server tag for TLS support.
- If TLS is supported starts the encryption negotiation. Used internally '''
- if not feats.getTag('starttls', namespace=NS_TLS):
- self.DEBUG("TLS unsupported by remote server.", 'warn')
- self.tls_start()
- return
- self.DEBUG("TLS supported by remote server. Requesting TLS start.", 'ok')
- self._owner.RegisterHandlerOnce('proceed', self.StartTLSHandler, xmlns=NS_TLS)
- self._owner.RegisterHandlerOnce('failure', self.StartTLSHandler, xmlns=NS_TLS)
- self._owner.send('' % NS_TLS)
- self.tls_start()
- raise NodeProcessed
-
- def _dumpX509(self, cert, stream=sys.stderr):
- print >> stream, "Digest (SHA-1):", cert.digest("sha1")
- print >> stream, "Digest (MD5):", cert.digest("md5")
- print >> stream, "Serial #:", cert.get_serial_number()
- print >> stream, "Version:", cert.get_version()
- print >> stream, "Expired:", torf(cert.has_expired(), "Yes", "No")
- print >> stream, "Subject:"
- self._dumpX509Name(cert.get_subject(), stream)
- print >> stream, "Issuer:"
- self._dumpX509Name(cert.get_issuer(), stream)
- self._dumpPKey(cert.get_pubkey(), stream)
-
- def _dumpX509Name(self, name, stream=sys.stderr):
- print >> stream, "X509Name:", str(name)
-
- def _dumpPKey(self, pkey, stream=sys.stderr):
- typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", OpenSSL.crypto.TYPE_DSA: "DSA"}
- print >> stream, "PKey bits:", pkey.bits()
- print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), "Unknown"), pkey.type())
-
- def _startSSL(self):
- ''' Immidiatedly switch socket to TLS mode. Used internally.'''
- log.debug("_startSSL called")
- if USE_PYOPENSSL: return self._startSSL_pyOpenSSL()
- return self._startSSL_stdlib()
-
- def _startSSL_pyOpenSSL(self):
- #log.debug("_startSSL_pyOpenSSL called, thread id: %s", str(thread.get_ident()))
- log.debug("_startSSL_pyOpenSSL called")
- tcpsock = self._owner.Connection
- # FIXME: should method be configurable?
- # Some gmail server don't support TLSv1, but only SSLv3, so use method
- # that allow SSLv2, v3 and TLSv1
- #tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
- tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
- tcpsock.ssl_errnum = 0
- tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
- cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
- try:
- tcpsock._sslContext.load_verify_locations(cacerts)
- except Exception:
- log.warning('Unable to load SSL certificats from file %s' % \
- os.path.abspath(cacerts))
- # load users certs
- if os.path.isfile(common.gajim.MY_CACERTS):
- store = tcpsock._sslContext.get_cert_store()
- f = open(common.gajim.MY_CACERTS)
- lines = f.readlines()
- i = 0
- begin = -1
- for line in lines:
- if 'BEGIN CERTIFICATE' in line:
- begin = i
- elif 'END CERTIFICATE' in line and begin > -1:
- cert = ''.join(lines[begin:i+2])
- try:
- X509cert = OpenSSL.crypto.load_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert)
- store.add_cert(X509cert)
- except OpenSSL.crypto.Error, exception_obj:
- log.warning('Unable to load a certificate from file %s: %s' %\
- (common.gajim.MY_CACERTS, exception_obj.args[0][0][2]))
- except:
- log.warning(
- 'Unknown error while loading certificate from file %s' % \
- common.gajim.MY_CACERTS)
- begin = -1
- i += 1
- tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
- tcpsock._sslObj.set_connect_state() # set to client mode
-
- wrapper = PyOpenSSLWrapper(tcpsock._sslObj)
- tcpsock._recv = wrapper.recv
- tcpsock._send = wrapper.send
-
- log.debug("Initiating handshake...")
- # FIXME: Figure out why _connect_success is called before the
- # SSL handshake is completed in STARTTLS mode. See #2838.
- tcpsock._sslObj.setblocking(True)
- try:
- self.starttls='in progress'
- tcpsock._sslObj.do_handshake()
- # Errors are handeled in _do_receive function
- except Exception:
- pass
- tcpsock._sslObj.setblocking(False)
- log.debug("Synchronous handshake completed")
- #log.debug("Async handshake started...")
-
- # fake it, for now
- self.starttls='success'
-
- def _startSSL_stdlib(self):
- log.debug("_startSSL_stdlib called")
- tcpsock=self._owner.Connection
- tcpsock._sock.setblocking(True)
- tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
- tcpsock._sock.setblocking(False)
- tcpsock._sslIssuer = tcpsock._sslObj.issuer()
- tcpsock._sslServer = tcpsock._sslObj.server()
- wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
- tcpsock._recv = wrapper.recv
- tcpsock._send = wrapper.send
- self.starttls='success'
-
- def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
- # Exceptions can't propagate up through this callback, so print them here.
- try:
- self._owner.Connection.ssl_fingerprint_sha1 = cert.digest('sha1')
- if errnum == 0:
- return True
- self._owner.Connection.ssl_errnum = errnum
- self._owner.Connection.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert)
- return True
- except Exception:
- log.error("Exception caught in _ssl_info_callback:", exc_info=True)
- traceback.print_exc() # Make sure something is printed, even if log is disabled.
-
- def StartTLSHandler(self, conn, starttls):
- ''' Handle server reply if TLS is allowed to process. Behaves accordingly.
- Used internally.'''
- if starttls.getNamespace() != NS_TLS:
- return
- self.starttls = starttls.getName()
- if self.starttls == 'failure':
- self.DEBUG('Got starttls response: ' + self.starttls,'error')
- return
- self.DEBUG('Got starttls proceed response. Switching to TLS/SSL...','ok')
- try:
- self._startSSL()
- except Exception:
- log.error("StartTLSHandler:", exc_info=True)
- #traceback.print_exc()
- self._owner.socket.pollend()
- return
- self._owner.Dispatcher.PlugOut()
- dispatcher_nb.Dispatcher().PlugIn(self._owner)
-
-
-class NBHTTPPROXYsocket(NonBlockingTcp):
- ''' This class can be used instead of transports.HTTPPROXYsocket
- HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
- redefines only connect method. Allows to use HTTP proxies like squid with
- (optionally) simple authentication (using login and password).
-
- '''
- def __init__(self, on_connect =None, on_proxy_failure=None, on_connect_failure = None,proxy = None,server = None,use_srv=True):
- ''' Caches proxy and target addresses.
- 'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
- and optional keys 'user' and 'password' to use for authentication.
- 'server' argument is a tuple of host and port - just like TCPsocket uses. '''
- self.on_connect_proxy = on_connect
- self.on_proxy_failure = on_proxy_failure
- self.on_connect_failure = on_connect_failure
- NonBlockingTcp.__init__(self, self._on_tcp_connect, on_connect_failure, server, use_srv)
- self.DBG_LINE=DBG_CONNECT_PROXY
- self.server = server
- self.proxy=proxy
-
- def plugin(self, owner):
- ''' Starts connection. Used interally. Returns non-empty string on success.'''
- owner.debug_flags.append(DBG_CONNECT_PROXY)
- NonBlockingTcp.plugin(self,owner)
-
- def connect(self,dupe=None):
- ''' Starts connection. Connects to proxy, supplies login and password to it
- (if were specified while creating instance). Instructs proxy to make
- connection to the target server. Returns non-empty sting on success. '''
- NonBlockingTcp.connect(self, (self.proxy['host'], self.proxy['port']))
-
- def _on_tcp_connect(self):
- self.DEBUG('Proxy server contacted, performing authentification','start')
- connector = ['CONNECT %s:%s HTTP/1.0'%self.server,
- 'Proxy-Connection: Keep-Alive',
- 'Pragma: no-cache',
- 'Host: %s:%s'%self.server,
- 'User-Agent: HTTPPROXYsocket/v0.1']
- if 'user' in self.proxy and 'password' in self.proxy:
- credentials = '%s:%s' % ( self.proxy['user'], self.proxy['password'])
- credentials = base64.encodestring(credentials).strip()
- connector.append('Proxy-Authorization: Basic '+credentials)
- connector.append('\r\n')
- self.onreceive(self._on_headers_sent)
- self.send('\r\n'.join(connector))
-
- def _on_headers_sent(self, reply):
- if reply is None:
- return
- self.reply = reply.replace('\r', '')
- try:
- proto, code, desc = reply.split('\n')[0].split(' ', 2)
- except Exception:
- log.error("_on_headers_sent:", exc_info=True)
- #traceback.print_exc()
- self.on_proxy_failure('Invalid proxy reply')
- return
- if code != '200':
- self.DEBUG('Invalid proxy reply: %s %s %s' % (proto, code, desc),'error')
- self._owner.disconnected()
- self.on_proxy_failure('Invalid proxy reply')
- return
- if len(reply) != 2:
- pass
- self.onreceive(self._on_proxy_auth)
-
- def _on_proxy_auth(self, reply):
- if self.reply.find('\n\n') == -1:
- if reply is None:
- self.on_proxy_failure('Proxy authentification failed')
- return
- if reply.find('\n\n') == -1:
- self.reply += reply.replace('\r', '')
- self.on_proxy_failure('Proxy authentification failed')
- return
- self.DEBUG('Authentification successfull. Jabber server contacted.','ok')
- if self.on_connect_proxy:
- self.on_connect_proxy()
-
- def DEBUG(self, text, severity):
- ''' Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy".'''
- return self._owner.DEBUG(DBG_CONNECT_PROXY, text, severity)
-
-class NBSOCKS5PROXYsocket(NonBlockingTcp):
- '''SOCKS5 proxy connection class. Uses TCPsocket as the base class
- redefines only connect method. Allows to use SOCKS5 proxies with
- (optionally) simple authentication (only USERNAME/PASSWORD auth).
- '''
- def __init__(self, on_connect = None, on_proxy_failure = None,
- on_connect_failure = None, proxy = None, server = None, use_srv = True):
- ''' Caches proxy and target addresses.
- 'proxy' argument is a dictionary with mandatory keys 'host' and 'port'
- (proxy address) and optional keys 'user' and 'password' to use for
- authentication. 'server' argument is a tuple of host and port -
- just like TCPsocket uses. '''
- self.on_connect_proxy = on_connect
- self.on_proxy_failure = on_proxy_failure
- self.on_connect_failure = on_connect_failure
- NonBlockingTcp.__init__(self, self._on_tcp_connect, on_connect_failure,
- server, use_srv)
- self.DBG_LINE=DBG_CONNECT_PROXY
- self.server = server
- self.proxy = proxy
- self.ipaddr = None
-
- def plugin(self, owner):
- ''' Starts connection. Used interally. Returns non-empty string on
- success.'''
- owner.debug_flags.append(DBG_CONNECT_PROXY)
- NonBlockingTcp.plugin(self, owner)
-
- def connect(self, dupe = None):
- ''' Starts connection. Connects to proxy, supplies login and password to
- it (if were specified while creating instance). Instructs proxy to make
- connection to the target server. Returns non-empty sting on success.
+ def _plug_idle(self, writable, readable):
'''
- NonBlockingTcp.connect(self, (self.proxy['host'], self.proxy['port']))
+ Plugs file descriptor of socket to Idlequeue. Plugged socket
+ will be watched for "send possible" or/and "recv possible" events. pollin()
+ callback is invoked on "recv possible", pollout() on "send_possible".
+ Plugged socket will always be watched for "error" event - in that case,
+ pollend() is called.
+ '''
+ log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable))
+ self.idlequeue.plug_idle(self, writable, readable)
- def _on_tcp_connect(self):
- self.DEBUG('Proxy server contacted, performing authentification', 'start')
- if 'user' in self.proxy and 'password' in self.proxy:
- to_send = '\x05\x02\x00\x02'
- else:
- to_send = '\x05\x01\x00'
- self.onreceive(self._on_greeting_sent)
- self.send(to_send)
- def _on_greeting_sent(self, reply):
- if reply is None:
- return
- if len(reply) != 2:
- self.on_proxy_failure('Invalid proxy reply')
- return
- if reply[0] != '\x05':
- self.DEBUG('Invalid proxy reply', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Invalid proxy reply')
- return
- if reply[1] == '\x00':
- return self._on_proxy_auth('\x01\x00')
- elif reply[1] == '\x02':
- to_send = '\x01' + chr(len(self.proxy['user'])) + self.proxy['user'] +\
- chr(len(self.proxy['password'])) + self.proxy['password']
- self.onreceive(self._on_proxy_auth)
- self.send(to_send)
- else:
- if reply[1] == '\xff':
- self.DEBUG('Authentification to proxy impossible: no acceptable '
- 'auth method', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Authentification to proxy impossible: no '
- 'acceptable authentification method')
- return
- self.DEBUG('Invalid proxy reply', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Invalid proxy reply')
- return
-
- def _on_proxy_auth(self, reply):
- if reply is None:
- return
- if len(reply) != 2:
- self.DEBUG('Invalid proxy reply', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Invalid proxy reply')
- return
- if reply[0] != '\x01':
- self.DEBUG('Invalid proxy reply', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Invalid proxy reply')
- return
- if reply[1] != '\x00':
- self.DEBUG('Authentification to proxy failed', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Authentification to proxy failed')
- return
- self.DEBUG('Authentification successfull. Jabber server contacted.','ok')
- # Request connection
- req = "\x05\x01\x00"
- # If the given destination address is an IP address, we'll
- # use the IPv4 address request even if remote resolving was specified.
+ def _do_send(self):
+ '''
+ Called when send() to connected socket is possible. First message from
+ sendqueue will be sent.
+ '''
+ if not self.sendbuff:
+ if not self.sendqueue:
+ log.warn('calling send on empty buffer and queue')
+ self._plug_idle(writable=False, readable=True)
+ return None
+ self.sendbuff = self.sendqueue.pop(0)
try:
- self.ipaddr = socket.inet_aton(self.server[0])
- req = req + "\x01" + self.ipaddr
- except socket.error:
- # Well it's not an IP number, so it's probably a DNS name.
-# if self.__proxy[3]==True:
- # Resolve remotely
- self.ipaddr = None
- req = req + "\x03" + chr(len(self.server[0])) + self.server[0]
-# else:
-# # Resolve locally
-# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.server[0]))
-# req = req + "\x01" + ipaddr
- req = req + struct.pack(">H",self.server[1])
- self.onreceive(self._on_req_sent)
- self.send(req)
+ send_count = self._send(self.sendbuff)
+ if send_count:
+ sent_data = self.sendbuff[:send_count]
+ self.sendbuff = self.sendbuff[send_count:]
+ self._plug_idle(
+ writable=((self.sendqueue!=[]) or (self.sendbuff!='')),
+ readable=True)
+ self.raise_event(DATA_SENT, sent_data)
- def _on_req_sent(self, reply):
- if reply is None:
+ except socket.error, e:
+ log.error('_do_send:', exc_info=True)
+ traceback.print_exc()
+ self.disconnect()
+
+ def _do_receive(self):
+ ''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.'''
+ received = None
+ errnum = 0
+ errstr = 'No Error Set'
+
+ try:
+ # get as many bites, as possible, but not more than RECV_BUFSIZE
+ received = self._recv(RECV_BUFSIZE)
+ except socket.error, (errnum, errstr):
+ log.info("_do_receive: got %s:" % received , exc_info=True)
+ except tls_nb.SSLWrapper.Error, e:
+ log.info("_do_receive, caught SSL error, got %s:" % received , exc_info=True)
+ errnum, errstr = e.exc
+
+ if received == '': errstr = 'zero bytes on recv'
+
+ if (self.ssl_lib is None and received == '') or \
+ (self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \
+ (self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ):
+ # 8 in stdlib: errstr == EOF occured in violation of protocol
+ # -1 in pyopenssl: errstr == Unexpected EOF
+ log.info("Disconnected by remote server: #%s, %s" % (errnum, errstr))
+ self.on_remote_disconnect()
return
- if len(reply) < 10:
- self.DEBUG('Invalid proxy reply', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Invalid proxy reply')
+
+
+ if errnum:
+ log.error("Connection to %s:%s lost: %s %s" % ( self.server, self.port, errnum, errstr), exc_info=True)
+ self.disconnect()
return
- if reply[0] != '\x05':
- self.DEBUG('Invalid proxy reply', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Invalid proxy reply')
+
+ # this branch is for case of non-fatal SSL errors - None is returned from
+ # recv() but no errnum is set
+
+ if received is None:
return
- if reply[1] != "\x00":
- # Connection failed
- self._owner.disconnected()
- if ord(reply[1])<9:
- errors = ['general SOCKS server failure',
- 'connection not allowed by ruleset',
- 'Network unreachable',
- 'Host unreachable',
- 'Connection refused',
- 'TTL expired',
- 'Command not supported',
- 'Address type not supported'
- ]
- txt = errors[ord(reply[1])-1]
- else:
- txt = 'Invalid proxy reply'
- self.DEBUG(txt, 'error')
- self.on_proxy_failure(txt)
- return
- # Get the bound address/port
- elif reply[3] == "\x01":
- pass # begin, end = 3, 7
- elif reply[3] == "\x03":
- pass # begin, end = 4, 4 + reply[4]
+
+ # we have received some bytes, stop the timeout!
+ self.renew_send_timeout()
+ # pass received data to owner
+ if self.on_receive:
+ self.raise_event(DATA_RECEIVED, received)
+ self._on_receive(received)
else:
- self.DEBUG('Invalid proxy reply', 'error')
- self._owner.disconnected()
- self.on_proxy_failure('Invalid proxy reply')
+ # This should never happen, so we need the debug. (If there is no handler
+ # on receive specified, data are passed to Dispatcher.ProcessNonBlocking)
+ log.error('SOCKET %s Unhandled data received: %s' % (id(self), received))
+ self.disconnect()
+
+ def _on_receive(self, data):
+ ''' preceeds on_receive callback. It peels off and checks HTTP headers in
+ class, in here it just calls the callback.'''
+ self.on_receive(data)
+
+
+class NonBlockingHTTP(NonBlockingTCP):
+ '''
+ Socket wrapper that creates HTTP message out of sent data and peels-off
+ HTTP headers from incoming messages
+ '''
+
+ def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls,
+ certs, on_http_request_possible, on_persistent_fallback, http_dict,
+ proxy_dict = None):
+ '''
+ :param on_http_request_possible: method to call when HTTP request to socket
+ owned by transport is possible.
+ :param on_persistent_fallback: callback called when server ends TCP
+ connection. It doesn't have to be fatal for HTTP session.
+ :param http_dict: dictionary with data for HTTP request and headers
+ '''
+
+ NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue,
+ estabilish_tls, certs, proxy_dict)
+
+ self.http_protocol, self.http_host, self.http_path = urisplit(http_dict['http_uri'])
+ if self.http_protocol is None:
+ self.http_protocol = 'http'
+ if self.http_path == '':
+ self.http_path = '/'
+ self.http_port = http_dict['http_port']
+ self.http_version = http_dict['http_version']
+ self.http_persistent = http_dict['http_persistent']
+ self.add_proxy_headers = http_dict['add_proxy_headers']
+ if http_dict.has_key('proxy_user') and http_dict.has_key('proxy_pass'):
+ self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict['proxy_pass']
+ else:
+ self.proxy_user, self.proxy_pass = None, None
+
+ # buffer for partial responses
+ self.recvbuff = ''
+ self.expected_length = 0
+ self.pending_requests = 0
+ self.on_http_request_possible = on_http_request_possible
+ self.last_recv_time = 0
+ self.close_current_connection = False
+ self.on_remote_disconnect = lambda: on_persistent_fallback(self)
+
+ def http_send(self, raw_data, now=False):
+ self.send(self.build_http_message(raw_data), now)
+
+ def _on_receive(self,data):
+ '''
+ Preceeds passing received data to owner class. Gets rid of HTTP headers
+ and checks them.
+ '''
+ if self.get_state() == PROXY_CONNECTING:
+ NonBlockingTCP._on_receive(self, data)
+ return
+ if not self.recvbuff:
+ # recvbuff empty - fresh HTTP message was received
+ try:
+ statusline, headers, self.recvbuff = self.parse_http_message(data)
+ except ValueError:
+ self.disconnect()
+ return
+ if statusline[1] != '200':
+ log.error('HTTP Error: %s %s' % (statusline[1], statusline[2]))
+ self.disconnect()
+ return
+ self.expected_length = int(headers['Content-Length'])
+ if headers.has_key('Connection') and headers['Connection'].strip()=='close':
+ self.close_current_connection = True
+ else:
+ #sth in recvbuff - append currently received data to HTTP msg in buffer
+ self.recvbuff = '%s%s' % (self.recvbuff, data)
+
+ if self.expected_length > len(self.recvbuff):
+ # If we haven't received the whole HTTP mess yet, let's end the thread.
+ # It will be finnished from one of following recvs on plugged socket.
+ log.info('not enough bytes in HTTP response - %d expected, %d got' %
+ (self.expected_length, len(self.recvbuff)))
return
- if self.on_connect_proxy:
- self.on_connect_proxy()
+ # everything was received
+ httpbody = self.recvbuff
- def DEBUG(self, text, severity):
- ''' Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy".'''
- return self._owner.DEBUG(DBG_CONNECT_PROXY, text, severity)
+ self.recvbuff=''
+ self.expected_length=0
+
+ if not self.http_persistent or self.close_current_connection:
+ # not-persistent connections disconnect after response
+ self.disconnect(do_callback=False)
+ self.close_current_connection = False
+ self.last_recv_time = time.time()
+ self.on_receive(data=httpbody, socket=self)
+ self.on_http_request_possible()
+
+
+ def build_http_message(self, httpbody, method='POST'):
+ '''
+ Builds http message with given body.
+ Values for headers and status line fields are taken from class variables.
+ '''
+ absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host,
+ self.http_port, self.http_path)
+ headers = ['%s %s %s' % (method, absolute_uri, self.http_version),
+ 'Host: %s:%s' % (self.http_host, self.http_port),
+ 'User-Agent: Gajim',
+ 'Content-Type: text/xml; charset=utf-8',
+ 'Content-Length: %s' % len(str(httpbody))]
+ if self.add_proxy_headers:
+ headers.append('Proxy-Connection: keep-alive')
+ headers.append('Pragma: no-cache')
+ if self.proxy_user and self.proxy_pass:
+ credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
+ credentials = base64.encodestring(credentials).strip()
+ headers.append('Proxy-Authorization: Basic %s' % credentials)
+ else:
+ headers.append('Connection: Keep-Alive')
+ headers.append('\r\n')
+ headers = '\r\n'.join(headers)
+ return('%s%s\r\n' % (headers, httpbody))
+
+ def parse_http_message(self, message):
+ '''
+ splits http message to tuple (
+ statusline - list of e.g. ['HTTP/1.1', '200', 'OK'],
+ headers - dictionary of headers e.g. {'Content-Length': '604',
+ 'Content-Type': 'text/xml; charset=utf-8'},
+ httpbody - string with http body
+ )
+ '''
+ message = message.replace('\r','')
+ (header, httpbody) = message.split('\n\n', 1)
+ header = header.split('\n')
+ statusline = header[0].split(' ', 2)
+ header = header[1:]
+ headers = {}
+ for dummy in header:
+ row = dummy.split(' ',1)
+ headers[row[0][:-1]] = row[1]
+ return (statusline, headers, httpbody)
+
+
+class NonBlockingHTTPBOSH(NonBlockingHTTP):
+ '''
+ Class for BOSH HTTP connections. Slightly redefines HTTP transport by calling
+ bosh bodytag generating callback before putting data on wire.
+ '''
+
+ def set_stanza_build_cb(self, build_cb):
+ self.build_cb = build_cb
+
+ def _do_send(self):
+ if self.state == PROXY_CONNECTING:
+ NonBlockingTCP._do_send(self)
+ return
+ if not self.sendbuff:
+ stanza = self.build_cb(socket=self)
+ stanza = self.encode_stanza(stanza)
+ stanza = self.build_http_message(httpbody=stanza)
+ self.sendbuff = stanza
+ NonBlockingTCP._do_send(self)
# vim: se ts=3:
diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py
index d3871147a..e0ac214cd 100644
--- a/src/common/zeroconf/client_zeroconf.py
+++ b/src/common/zeroconf/client_zeroconf.py
@@ -23,6 +23,7 @@ from common.xmpp.idlequeue import IdleObject
from common.xmpp import dispatcher_nb, simplexml
from common.xmpp.client import *
from common.xmpp.simplexml import ustr
+from common.xmpp.transports_nb import DATA_RECEIVED, DATA_SENT
from common.zeroconf import zeroconf
from common.xmpp.protocol import *
@@ -36,8 +37,6 @@ log = logging.getLogger('gajim.c.z.client_zeroconf')
from common.zeroconf import roster_zeroconf
MAX_BUFF_LEN = 65536
-DATA_RECEIVED = 'DATA RECEIVED'
-DATA_SENT = 'DATA SENT'
TYPE_SERVER, TYPE_CLIENT = range(2)
# wait XX sec to establish a connection
@@ -120,6 +119,7 @@ class P2PClient(IdleObject):
on_ok=None, on_not_ok=None):
self._owner = self
self.Namespace = 'jabber:client'
+ self.protocol_type = 'XMPP'
self.defaultNamespace = self.Namespace
self._component = 0
self._registered_name = None
@@ -130,16 +130,7 @@ class P2PClient(IdleObject):
self.Server = host
self.on_ok = on_ok
self.on_not_ok = on_not_ok
- self.DBG = 'client'
self.Connection = None
- if gajim.verbose:
- debug = ['always', 'nodebuilder']
- else:
- debug = []
- self._DEBUG = Debug.Debug(debug)
- self.DEBUG = self._DEBUG.Show
- self.debug_flags = self._DEBUG.debug_flags
- self.debug_flags.append(self.DBG)
self.sock_hash = None
if _sock:
self.sock_type = TYPE_SERVER
@@ -204,8 +195,6 @@ class P2PClient(IdleObject):
self.Dispatcher.Stream._dispatch_depth = 2
self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
self.Dispatcher.Stream.stream_header_received = self._check_stream_start
- self.debug_flags.append(simplexml.DBG_NODEBUILDER)
- self.Dispatcher.Stream.DEBUG = self.DEBUG
self.Dispatcher.Stream.features = None
if self.sock_type == TYPE_CLIENT:
self.send_stream_header()
@@ -223,8 +212,7 @@ class P2PClient(IdleObject):
def _check_stream_start(self, ns, tag, attrs):
if ns != NS_STREAMS or tag != 'stream':
- self.Connection.DEBUG('Incorrect stream start: (%s,%s).Terminating! ' \
- % (tag, ns), 'error')
+ log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag, ns), 'error')
self.Connection.disconnect()
if self.on_not_ok:
self.on_not_ok('Connection to host could not be established: Incorrect answer from server.')
@@ -301,7 +289,6 @@ class P2PConnection(IdleObject, PlugIn):
IdleObject.__init__(self)
self._owner = client
PlugIn.__init__(self)
- self.DBG_LINE = 'socket'
self.sendqueue = []
self.sendbuff = None
self.buff_is_message = False
@@ -474,13 +461,13 @@ class P2PConnection(IdleObject, PlugIn):
if self._owner.sock_type == TYPE_CLIENT:
self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
if received.strip():
- self.DEBUG(received, 'got')
+ log.debug('received: %s', received)
if hasattr(self._owner, 'Dispatcher'):
self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
self.on_receive(received)
else:
# This should never happed, so we need the debug
- self.DEBUG('Unhandled data received: %s' % received,'error')
+ log.error('Unhandled data received: %s' % received)
self.disconnect()
return True
@@ -543,7 +530,7 @@ class P2PConnection(IdleObject, PlugIn):
def _on_send(self):
if self.sent_data and self.sent_data.strip():
- self.DEBUG(self.sent_data,'sent')
+ log.debug('sent: %s' % self.sent_data)
if hasattr(self._owner, 'Dispatcher'):
self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
self.sent_data = None
@@ -552,7 +539,7 @@ class P2PConnection(IdleObject, PlugIn):
self.buff_is_message = False
def _on_send_failure(self):
- self.DEBUG("Socket error while sending data",'error')
+ log.error('Socket error while sending data')
self._owner.disconnected()
self.sent_data = None
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
index d614f90d8..ceb322617 100644
--- a/src/common/zeroconf/connection_zeroconf.py
+++ b/src/common/zeroconf/connection_zeroconf.py
@@ -138,6 +138,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
if event in gajim.handlers:
gajim.handlers[event](self.name, data)
+
def _reconnect(self):
# Do not try to reco while we are already trying
self.time_to_reconnect = None
@@ -472,19 +473,20 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
else:
kind = 'single_msg_sent'
gajim.logger.write(kind, jid, log_msg)
-
+
self.dispatch('MSGSENT', (jid, msg, keyID))
def on_send_not_ok(reason):
reason += ' ' + _('Your message could not be sent.')
self.dispatch('MSGERROR', [jid, -1, reason, None, None, session])
-
ret = self.connection.send(msg_iq, msg is not None, on_ok=on_send_ok,
on_not_ok=on_send_not_ok)
if ret == -1:
# Contact Offline
self.dispatch('MSGERROR', [jid, -1, _('Contact is offline. Your message could not be sent.'), None, None, session])
return ret
+ return ret
+
def send_stanza(self, stanza):
# send a stanza untouched
@@ -571,9 +573,9 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
def _event_dispatcher(self, realm, event, data):
if realm == '':
- if event == common.xmpp.transports.DATA_RECEIVED:
+ if event == common.xmpp.transports_nb.DATA_RECEIVED:
self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
- elif event == common.xmpp.transports.DATA_SENT:
+ elif event == common.xmpp.transports_nb.DATA_SENT:
self.dispatch('STANZA_SENT', unicode(data))
elif event == common.xmpp.transports.DATA_ERROR:
thread_id = data[1]
diff --git a/src/config.py b/src/config.py
index b997823bd..5a5ef8f09 100644
--- a/src/config.py
+++ b/src/config.py
@@ -1130,9 +1130,30 @@ class ManageProxiesWindow:
self.proxies_treeview = self.xml.get_widget('proxies_treeview')
self.proxyname_entry = self.xml.get_widget('proxyname_entry')
self.proxytype_combobox = self.xml.get_widget('proxytype_combobox')
+
self.init_list()
self.xml.signal_autoconnect(self)
self.window.show_all()
+ # hide the BOSH fields by default
+ self.show_bosh_fields()
+
+ def show_bosh_fields(self, show=True):
+ if show:
+ self.xml.get_widget('boshuri_entry').show()
+ self.xml.get_widget('boshport_entry').show()
+ self.xml.get_widget('boshuri_label').show()
+ self.xml.get_widget('boshport_label').show()
+ self.xml.get_widget('boshuseproxy_checkbutton').show()
+ else:
+ cb = self.xml.get_widget('boshuseproxy_checkbutton')
+ cb.hide()
+ cb.set_active(True)
+ self.on_boshuseproxy_checkbutton_toggled(cb)
+ self.xml.get_widget('boshuri_entry').hide()
+ self.xml.get_widget('boshport_entry').hide()
+ self.xml.get_widget('boshuri_label').hide()
+ self.xml.get_widget('boshport_label').hide()
+
def fill_proxies_treeview(self):
model = self.proxies_treeview.get_model()
@@ -1187,9 +1208,18 @@ class ManageProxiesWindow:
def on_useauth_checkbutton_toggled(self, widget):
act = widget.get_active()
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'useauth', act)
self.xml.get_widget('proxyuser_entry').set_sensitive(act)
self.xml.get_widget('proxypass_entry').set_sensitive(act)
+ def on_boshuseproxy_checkbutton_toggled(self, widget):
+ act = widget.get_active()
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'bosh_useproxy', act)
+ self.xml.get_widget('proxyhost_entry').set_sensitive(act)
+ self.xml.get_widget('proxyport_entry').set_sensitive(act)
+
def on_proxies_treeview_cursor_changed(self, widget):
#FIXME: check if off proxy settings are correct (see
# http://trac.gajim.org/changeset/1921#file2 line 1221
@@ -1202,19 +1232,33 @@ class ManageProxiesWindow:
proxyport_entry = self.xml.get_widget('proxyport_entry')
proxyuser_entry = self.xml.get_widget('proxyuser_entry')
proxypass_entry = self.xml.get_widget('proxypass_entry')
+ boshuri_entry = self.xml.get_widget('boshuri_entry')
+ boshport_entry = self.xml.get_widget('boshport_entry')
useauth_checkbutton = self.xml.get_widget('useauth_checkbutton')
+ boshuseproxy_checkbutton = self.xml.get_widget('boshuseproxy_checkbutton')
proxyhost_entry.set_text('')
proxyport_entry.set_text('')
proxyuser_entry.set_text('')
proxypass_entry.set_text('')
- useauth_checkbutton.set_active(False)
- self.on_useauth_checkbutton_toggled(useauth_checkbutton)
+ boshuri_entry.set_text('')
+
+ #boshuseproxy_checkbutton.set_active(False)
+ #self.on_boshuseproxy_checkbutton_toggled(boshuseproxy_checkbutton)
+
+ #useauth_checkbutton.set_active(False)
+ #self.on_useauth_checkbutton_toggled(useauth_checkbutton)
+
if proxy == _('None'): # special proxy None
+ self.show_bosh_fields(False)
self.proxyname_entry.set_editable(False)
self.xml.get_widget('remove_proxy_button').set_sensitive(False)
self.xml.get_widget('proxytype_combobox').set_sensitive(False)
self.xml.get_widget('proxy_table').set_sensitive(False)
else:
+ proxytype = gajim.config.get_per('proxies', proxy, 'type')
+
+ self.show_bosh_fields(proxytype=='bosh')
+
self.proxyname_entry.set_editable(True)
self.xml.get_widget('remove_proxy_button').set_sensitive(True)
self.xml.get_widget('proxytype_combobox').set_sensitive(True)
@@ -1227,11 +1271,16 @@ class ManageProxiesWindow:
'user'))
proxypass_entry.set_text(gajim.config.get_per('proxies', proxy,
'pass'))
- proxytype = gajim.config.get_per('proxies', proxy, 'type')
- types = ['http', 'socks5']
+ boshuri_entry.set_text(gajim.config.get_per('proxies', proxy,
+ 'bosh_uri'))
+ boshport_entry.set_text(unicode(gajim.config.get_per('proxies', proxy,
+ 'bosh_port')))
+ types = ['http', 'socks5', 'bosh']
self.proxytype_combobox.set_active(types.index(proxytype))
- if gajim.config.get_per('proxies', proxy, 'user'):
- useauth_checkbutton.set_active(True)
+ boshuseproxy_checkbutton.set_active(
+ gajim.config.get_per('proxies', proxy, 'bosh_useproxy'))
+ useauth_checkbutton.set_active(
+ gajim.config.get_per('proxies', proxy, 'useauth'))
def on_proxies_treeview_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Delete:
@@ -1256,8 +1305,9 @@ class ManageProxiesWindow:
model.set_value(iter_, 0, new_name)
def on_proxytype_combobox_changed(self, widget):
- types = ['http', 'socks5']
+ types = ['http', 'socks5', 'bosh']
type_ = self.proxytype_combobox.get_active()
+ self.show_bosh_fields(types[type_]=='bosh')
proxy = self.proxyname_entry.get_text().decode('utf-8')
gajim.config.set_per('proxies', proxy, 'type', types[type_])
@@ -1276,6 +1326,16 @@ class ManageProxiesWindow:
proxy = self.proxyname_entry.get_text().decode('utf-8')
gajim.config.set_per('proxies', proxy, 'user', value)
+ def on_boshuri_entry_changed(self, widget):
+ value = widget.get_text().decode('utf-8')
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'bosh_uri', value)
+
+ def on_boshport_entry_changed(self, widget):
+ value = widget.get_text().decode('utf-8')
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'bosh_port', value)
+
def on_proxypass_entry_changed(self, widget):
value = widget.get_text().decode('utf-8')
proxy = self.proxyname_entry.get_text().decode('utf-8')
diff --git a/src/dialogs.py b/src/dialogs.py
index 7fcd44f40..9c0e1a3d6 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -3175,7 +3175,8 @@ class AddSpecialNotificationDialog:
active = widget.get_active()
if active == 1: # user selected 'choose sound'
def on_ok(widget, path_to_snd_file):
- print path_to_snd_file
+ pass
+ #print path_to_snd_file
def on_cancel(widget):
widget.set_active(0) # go back to No Sound
@@ -3187,11 +3188,9 @@ class AddSpecialNotificationDialog:
conditions = ('online', 'chat', 'online_and_chat',
'away', 'xa', 'away_and_xa', 'dnd', 'xa_and_dnd', 'offline')
active = self.condition_combobox.get_active()
- print conditions[active]
active_iter = self.listen_sound_combobox.get_active_iter()
listen_sound_model = self.listen_sound_combobox.get_model()
- print listen_sound_model[active_iter][0]
class AdvancedNotificationsWindow:
events_list = ['message_received', 'contact_connected',
diff --git a/src/gajim.py b/src/gajim.py
index 5190b8b06..992899c09 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -70,7 +70,8 @@ import logging
consoleloghandler = logging.StreamHandler()
consoleloghandler.setLevel(1)
consoleloghandler.setFormatter(
-logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s'))
+ logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s')
+ )
log = logging.getLogger('gajim')
log.setLevel(logging.WARNING)
log.addHandler(consoleloghandler)
@@ -259,7 +260,7 @@ import common.sleepy
from common.xmpp import idlequeue
from common.zeroconf import connection_zeroconf
-from common import nslookup
+from common import resolver
from common import proxy65_manager
from common import socks5
from common import helpers
@@ -618,7 +619,7 @@ class Interface:
ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
def handle_event_con_type(self, account, con_type):
- # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'tcp'
+ # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain'
gajim.con_types[account] = con_type
self.roster.draw_account(account)
@@ -3158,7 +3159,7 @@ class Interface:
# gajim.idlequeue.process() each foo miliseconds
gajim.idlequeue = GlibIdleQueue()
# resolve and keep current record of resolved hosts
- gajim.resolver = nslookup.Resolver(gajim.idlequeue)
+ gajim.resolver = resolver.get_resolver(gajim.idlequeue)
gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
self.handle_event_file_rcv_completed,
self.handle_event_file_progress,
@@ -3315,6 +3316,10 @@ class Interface:
gobject.timeout_add_seconds(gajim.config.get(
'check_idle_every_foo_seconds'), self.read_sleepy)
+ # when using libasyncns we need to process resolver in regular intervals
+ if resolver.USE_LIBASYNCNS:
+ gobject.timeout_add(200, gajim.resolver.process)
+
def remote_init():
if gajim.config.get('remote_control'):
try:
diff --git a/test/test_client_nb.py b/test/test_client_nb.py
new file mode 100644
index 000000000..ad979819c
--- /dev/null
+++ b/test/test_client_nb.py
@@ -0,0 +1,932 @@
+import unittest
+from xmpp_mocks import *
+
+import sys, os.path
+
+gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
+
+sys.path.append(gajim_root + '/src/common/xmpp')
+
+import client_nb
+
+'''
+Testing script for NonBlockingClient class (src/common/xmpp/client_nb.py)
+It actually connects to a xmpp server so the connection values have to be
+changed before running.
+'''
+
+
+xmpp_server_port = ('gajim.org',5222)
+'''
+2-tuple - (XMPP server hostname, c2s port)
+Script will connect to the machine.
+'''
+
+credentials = ['loginn', 'passwo', 'testresour']
+'''
+[username, password, passphrase]
+Script will autheticate itself with this credentials on above mentioned server.
+'''
+
+class TestNonBlockingClient(unittest.TestCase):
+ '''
+ Test Cases class for NonBlockingClient.
+ '''
+
+ def setUp(self):
+ '''
+ IdleQueue thread is run and dummy connection is created.
+ '''
+
+ self.idlequeue_thread = IdleQueueThread()
+ self.connection = MockConnectionClass()
+
+ self.idlequeue_thread.start()
+
+ def tearDown(self):
+ '''
+ IdleQueue thread is stopped.
+ '''
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ def open_stream(self, server_port):
+ '''
+ Method opening the XMPP connection. It returns when
+ is received from server.
+
+ :param server_port: tuple of (hostname, port) for where the client should
+ connect.
+
+ '''
+ self.client = client_nb.NonBlockingClient(
+ domain=server_port[0],
+ idlequeue=self.idlequeue_thread.iq,
+ )
+ '''
+ NonBlockingClient instance with parameters from global variables and with
+ callbacks from dummy connection.
+ '''
+
+ self.client.connect(
+ on_connect=lambda *args: self.connection.on_connect(True, *args),
+ on_connect_failure=lambda *args: self.connection.on_connect(False, *args),
+ )
+
+ print 'waiting for callback from client constructor'
+ self.connection.wait()
+
+ # if on_connect was called, client has to be connected and vice versa
+ if self.connection.connect_succeeded:
+ self.assert_(self.client.get_connect_type())
+ else:
+ self.assert_(not self.client.get_connect_type())
+
+ def client_auth(self, username, password, resource, sasl):
+ '''
+ Method authenticating connected client with supplied credentials. Returns
+ when authentication is over.
+ :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication
+
+ :todo: to check and be more specific about when it returns (bind, session..)
+ '''
+ self.client.auth(username, password, resource, sasl,
+ on_auth=self.connection.on_auth)
+
+ print 'waiting for authentication...'
+ self.connection.wait()
+
+ def do_disconnect(self):
+ '''
+ Does disconnecting of connected client. Returns when TCP connection is closed.
+ '''
+ #self.client.start_disconnect(None, on_disconnect=self.connection.set_event)
+ self.client.RegisterDisconnectHandler(self.connection.set_event)
+ self.client.disconnect()
+
+ print 'waiting for disconnecting...'
+ self.connection.wait()
+
+ def test_proper_connect_sasl(self):
+ '''
+ The ideal testcase - client is connected, authenticated with SASL and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+
+ # if client is not connected, lets raise the AssertionError
+ self.assert_(self.client.get_connect_type())
+ # (client.disconnect() is already called from NBClient._on_connected_failure
+ # so there's need to call it in this case
+
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='sasl')
+
+ self.do_disconnect()
+
+
+ def test_proper_connect_oldauth(self):
+ '''
+ The ideal testcase - client is connected, authenticated with old auth and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='old_auth')
+ self.do_disconnect()
+
+ def test_connect_to_nonexisting_host(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return nothing.
+ '''
+ self.open_stream(('fdsfsdf.fdsf.fss', 5222))
+ print 'nonexthost: %s' % self.client.get_connect_type()
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_to_wrong_port(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return some IP
+ but there shouldn't be XMPP server running on specified port.
+ '''
+ self.open_stream((xmpp_server_port[0], 31337))
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_with_wrong_creds(self):
+ '''
+ Connecting with invalid password.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], "wrong pass", credentials[2], sasl=1)
+ self.assert_(self.connection.auth is None)
+ self.do_disconnect()
+
+
+
+
+
+
+if __name__ == '__main__':
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestNonBlockingClient)
+ #suite = unittest.TestSuite()
+ #suite.addTest(TestNonBlockingClient('test_proper_connect_oldauth'))
+ #suite.addTest(TestNonBlockingClient('test_connect_to_nonexisting_host'))
+
+ unittest.TextTestRunner(verbosity=2).run(suite)
+
+
+
+
+
+import unittest
+from xmpp_mocks import *
+
+import sys, os.path
+
+gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
+
+sys.path.append(gajim_root + '/src/common/xmpp')
+
+import client_nb
+
+'''
+Testing script for NonBlockingClient class (src/common/xmpp/client_nb.py)
+It actually connects to a xmpp server so the connection values have to be
+changed before running.
+'''
+
+
+xmpp_server_port = ('xmpp.example.org',5222)
+'''
+2-tuple - (XMPP server hostname, c2s port)
+Script will connect to the machine.
+'''
+
+credentials = ['loginn', 'passwo', 'testresour']
+'''
+[username, password, passphrase]
+Script will autheticate itself with this credentials on above mentioned server.
+'''
+
+class TestNonBlockingClient(unittest.TestCase):
+ '''
+ Test Cases class for NonBlockingClient.
+ '''
+
+ def setUp(self):
+ '''
+ IdleQueue thread is run and dummy connection is created.
+ '''
+
+ self.idlequeue_thread = IdleQueueThread()
+ self.connection = MockConnectionClass()
+
+ self.idlequeue_thread.start()
+
+ def tearDown(self):
+ '''
+ IdleQueue thread is stopped.
+ '''
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ def open_stream(self, server_port):
+ '''
+ Method opening the XMPP connection. It returns when
+ is received from server.
+
+ :param server_port: tuple of (hostname, port) for where the client should
+ connect.
+
+ '''
+ self.client = client_nb.NonBlockingClient(
+ hostname=server_port[0],
+ port=server_port[1],
+ caller=self.connection,
+ idlequeue=self.idlequeue_thread.iq,
+ )
+ '''
+ NonBlockingClient instance with parameters from global variables and with
+ callbacks from dummy connection.
+ '''
+
+ self.client.connect(
+ on_connect=lambda *args: self.connection.on_connect(True, *args),
+ on_connect_failure=lambda *args: self.connection.on_connect(False, *args),
+ secure=False
+ )
+
+ print 'waiting for callback from client constructor'
+ self.connection.wait()
+
+ # if on_connect was called, client has to be connected and vice versa
+ if self.connection.connect_succeeded:
+ self.assert_(self.client.get_connect_type())
+ else:
+ self.assert_(not self.client.get_connect_type())
+
+ def client_auth(self, username, password, resource, sasl):
+ '''
+ Method authenticating connected client with supplied credentials. Returns
+ when authentication is over.
+ :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication
+
+ :todo: to check and be more specific about when it returns (bind, session..)
+ '''
+ self.client.auth(username, password, resource, sasl,
+ on_auth=self.connection.on_auth)
+
+ print 'waiting for authentication...'
+ self.connection.wait()
+
+ def do_disconnect(self):
+ '''
+ Does disconnecting of connected client. Returns when TCP connection is closed.
+ '''
+ #self.client.start_disconnect(None, on_disconnect=self.connection.set_event)
+ self.client.RegisterDisconnectHandler(self.connection.set_event)
+ self.client.disconnect()
+
+ print 'waiting for disconnecting...'
+ self.connection.wait()
+
+ def test_proper_connect_sasl(self):
+ '''
+ The ideal testcase - client is connected, authenticated with SASL and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+
+ # if client is not connected, lets raise the AssertionError
+ self.assert_(self.client.get_connect_type())
+ # (client.disconnect() is already called from NBClient._on_connected_failure
+ # so there's need to call it in this case
+
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='sasl')
+
+ self.do_disconnect()
+
+
+ def test_proper_connect_oldauth(self):
+ '''
+ The ideal testcase - client is connected, authenticated with old auth and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='old_auth')
+ self.do_disconnect()
+
+ def test_connect_to_nonexisting_host(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return nothing.
+ '''
+ self.open_stream(('fdsfsdf.fdsf.fss', 5222))
+ print 'nonexthost: %s' % self.client.get_connect_type()
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_to_wrong_port(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return some IP
+ but there shouldn't be XMPP server running on specified port.
+ '''
+ self.open_stream((xmpp_server_port[0], 31337))
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_with_wrong_creds(self):
+ '''
+ Connecting with invalid password.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], "wrong pass", credentials[2], sasl=1)
+ self.assert_(self.connection.auth is None)
+ self.do_disconnect()
+
+
+
+
+
+
+if __name__ == '__main__':
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestNonBlockingClient)
+ #suite = unittest.TestSuite()
+ #suite.addTest(TestNonBlockingClient('test_proper_connect_oldauth'))
+ #suite.addTest(TestNonBlockingClient('test_connect_to_nonexisting_host'))
+
+ unittest.TextTestRunner(verbosity=2).run(suite)
+
+
+
+
+
+import unittest
+from xmpp_mocks import *
+
+import sys, os.path
+
+gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
+
+sys.path.append(gajim_root + '/src/common/xmpp')
+
+import client_nb
+
+'''
+Testing script for NonBlockingClient class (src/common/xmpp/client_nb.py)
+It actually connects to a xmpp server so the connection values have to be
+changed before running.
+'''
+
+
+xmpp_server_port = ('xmpp.example.org',5222)
+'''
+2-tuple - (XMPP server hostname, c2s port)
+Script will connect to the machine.
+'''
+
+credentials = ['loginn', 'passwo', 'testresour']
+'''
+[username, password, passphrase]
+Script will autheticate itself with this credentials on above mentioned server.
+'''
+
+class TestNonBlockingClient(unittest.TestCase):
+ '''
+ Test Cases class for NonBlockingClient.
+ '''
+
+ def setUp(self):
+ '''
+ IdleQueue thread is run and dummy connection is created.
+ '''
+
+ self.idlequeue_thread = IdleQueueThread()
+ self.connection = MockConnectionClass()
+
+ self.idlequeue_thread.start()
+
+ def tearDown(self):
+ '''
+ IdleQueue thread is stopped.
+ '''
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ def open_stream(self, server_port):
+ '''
+ Method opening the XMPP connection. It returns when
+ is received from server.
+
+ :param server_port: tuple of (hostname, port) for where the client should
+ connect.
+
+ '''
+ self.client = client_nb.NonBlockingClient(
+ hostname=server_port[0],
+ port=server_port[1],
+ caller=self.connection,
+ idlequeue=self.idlequeue_thread.iq,
+ )
+ '''
+ NonBlockingClient instance with parameters from global variables and with
+ callbacks from dummy connection.
+ '''
+
+ self.client.connect(
+ on_connect=lambda *args: self.connection.on_connect(True, *args),
+ on_connect_failure=lambda *args: self.connection.on_connect(False, *args),
+ secure=False
+ )
+
+ print 'waiting for callback from client constructor'
+ self.connection.wait()
+
+ # if on_connect was called, client has to be connected and vice versa
+ if self.connection.connect_succeeded:
+ self.assert_(self.client.get_connect_type())
+ else:
+ self.assert_(not self.client.get_connect_type())
+
+ def client_auth(self, username, password, resource, sasl):
+ '''
+ Method authenticating connected client with supplied credentials. Returns
+ when authentication is over.
+ :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication
+
+ :todo: to check and be more specific about when it returns (bind, session..)
+ '''
+ self.client.auth(username, password, resource, sasl,
+ on_auth=self.connection.on_auth)
+
+ print 'waiting for authentication...'
+ self.connection.wait()
+
+ def do_disconnect(self):
+ '''
+ Does disconnecting of connected client. Returns when TCP connection is closed.
+ '''
+ #self.client.start_disconnect(None, on_disconnect=self.connection.set_event)
+ self.client.RegisterDisconnectHandler(self.connection.set_event)
+ self.client.disconnect()
+
+ print 'waiting for disconnecting...'
+ self.connection.wait()
+
+ def test_proper_connect_sasl(self):
+ '''
+ The ideal testcase - client is connected, authenticated with SASL and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+
+ # if client is not connected, lets raise the AssertionError
+ self.assert_(self.client.get_connect_type())
+ # (client.disconnect() is already called from NBClient._on_connected_failure
+ # so there's need to call it in this case
+
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='sasl')
+
+ self.do_disconnect()
+
+
+ def test_proper_connect_oldauth(self):
+ '''
+ The ideal testcase - client is connected, authenticated with old auth and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='old_auth')
+ self.do_disconnect()
+
+ def test_connect_to_nonexisting_host(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return nothing.
+ '''
+ self.open_stream(('fdsfsdf.fdsf.fss', 5222))
+ print 'nonexthost: %s' % self.client.get_connect_type()
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_to_wrong_port(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return some IP
+ but there shouldn't be XMPP server running on specified port.
+ '''
+ self.open_stream((xmpp_server_port[0], 31337))
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_with_wrong_creds(self):
+ '''
+ Connecting with invalid password.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], "wrong pass", credentials[2], sasl=1)
+ self.assert_(self.connection.auth is None)
+ self.do_disconnect()
+
+
+
+
+
+
+if __name__ == '__main__':
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestNonBlockingClient)
+ #suite = unittest.TestSuite()
+ #suite.addTest(TestNonBlockingClient('test_proper_connect_oldauth'))
+ #suite.addTest(TestNonBlockingClient('test_connect_to_nonexisting_host'))
+
+ unittest.TextTestRunner(verbosity=2).run(suite)
+
+
+
+
+
+import unittest
+from xmpp_mocks import *
+
+import sys, os.path
+
+gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
+
+sys.path.append(gajim_root + '/src/common/xmpp')
+
+import client_nb
+
+'''
+Testing script for NonBlockingClient class (src/common/xmpp/client_nb.py)
+It actually connects to a xmpp server so the connection values have to be
+changed before running.
+'''
+
+
+xmpp_server_port = ('xmpp.example.org',5222)
+'''
+2-tuple - (XMPP server hostname, c2s port)
+Script will connect to the machine.
+'''
+
+credentials = ['loginn', 'passwo', 'testresour']
+'''
+[username, password, passphrase]
+Script will autheticate itself with this credentials on above mentioned server.
+'''
+
+class TestNonBlockingClient(unittest.TestCase):
+ '''
+ Test Cases class for NonBlockingClient.
+ '''
+
+ def setUp(self):
+ '''
+ IdleQueue thread is run and dummy connection is created.
+ '''
+
+ self.idlequeue_thread = IdleQueueThread()
+ self.connection = MockConnectionClass()
+
+ self.idlequeue_thread.start()
+
+ def tearDown(self):
+ '''
+ IdleQueue thread is stopped.
+ '''
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ def open_stream(self, server_port):
+ '''
+ Method opening the XMPP connection. It returns when
+ is received from server.
+
+ :param server_port: tuple of (hostname, port) for where the client should
+ connect.
+
+ '''
+ self.client = client_nb.NonBlockingClient(
+ hostname=server_port[0],
+ port=server_port[1],
+ caller=self.connection,
+ idlequeue=self.idlequeue_thread.iq,
+ )
+ '''
+ NonBlockingClient instance with parameters from global variables and with
+ callbacks from dummy connection.
+ '''
+
+ self.client.connect(
+ on_connect=lambda *args: self.connection.on_connect(True, *args),
+ on_connect_failure=lambda *args: self.connection.on_connect(False, *args),
+ secure=False
+ )
+
+ print 'waiting for callback from client constructor'
+ self.connection.wait()
+
+ # if on_connect was called, client has to be connected and vice versa
+ if self.connection.connect_succeeded:
+ self.assert_(self.client.get_connect_type())
+ else:
+ self.assert_(not self.client.get_connect_type())
+
+ def client_auth(self, username, password, resource, sasl):
+ '''
+ Method authenticating connected client with supplied credentials. Returns
+ when authentication is over.
+ :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication
+
+ :todo: to check and be more specific about when it returns (bind, session..)
+ '''
+ self.client.auth(username, password, resource, sasl,
+ on_auth=self.connection.on_auth)
+
+ print 'waiting for authentication...'
+ self.connection.wait()
+
+ def do_disconnect(self):
+ '''
+ Does disconnecting of connected client. Returns when TCP connection is closed.
+ '''
+ #self.client.start_disconnect(None, on_disconnect=self.connection.set_event)
+ self.client.RegisterDisconnectHandler(self.connection.set_event)
+ self.client.disconnect()
+
+ print 'waiting for disconnecting...'
+ self.connection.wait()
+
+ def test_proper_connect_sasl(self):
+ '''
+ The ideal testcase - client is connected, authenticated with SASL and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+
+ # if client is not connected, lets raise the AssertionError
+ self.assert_(self.client.get_connect_type())
+ # (client.disconnect() is already called from NBClient._on_connected_failure
+ # so there's need to call it in this case
+
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='sasl')
+
+ self.do_disconnect()
+
+
+ def test_proper_connect_oldauth(self):
+ '''
+ The ideal testcase - client is connected, authenticated with old auth and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='old_auth')
+ self.do_disconnect()
+
+ def test_connect_to_nonexisting_host(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return nothing.
+ '''
+ self.open_stream(('fdsfsdf.fdsf.fss', 5222))
+ print 'nonexthost: %s' % self.client.get_connect_type()
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_to_wrong_port(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return some IP
+ but there shouldn't be XMPP server running on specified port.
+ '''
+ self.open_stream((xmpp_server_port[0], 31337))
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_with_wrong_creds(self):
+ '''
+ Connecting with invalid password.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], "wrong pass", credentials[2], sasl=1)
+ self.assert_(self.connection.auth is None)
+ self.do_disconnect()
+
+
+
+
+
+
+if __name__ == '__main__':
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestNonBlockingClient)
+ #suite = unittest.TestSuite()
+ #suite.addTest(TestNonBlockingClient('test_proper_connect_oldauth'))
+ #suite.addTest(TestNonBlockingClient('test_connect_to_nonexisting_host'))
+
+ unittest.TextTestRunner(verbosity=2).run(suite)
+
+
+
+
+
+import unittest
+from xmpp_mocks import *
+
+import sys, os.path
+
+gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
+
+sys.path.append(gajim_root + '/src/common/xmpp')
+
+import client_nb
+
+'''
+Testing script for NonBlockingClient class (src/common/xmpp/client_nb.py)
+It actually connects to a xmpp server so the connection values have to be
+changed before running.
+'''
+
+
+xmpp_server_port = ('xmpp.example.org',5222)
+'''
+2-tuple - (XMPP server hostname, c2s port)
+Script will connect to the machine.
+'''
+
+credentials = ['loginn', 'passwo', 'testresour']
+'''
+[username, password, passphrase]
+Script will autheticate itself with this credentials on above mentioned server.
+'''
+
+class TestNonBlockingClient(unittest.TestCase):
+ '''
+ Test Cases class for NonBlockingClient.
+ '''
+
+ def setUp(self):
+ '''
+ IdleQueue thread is run and dummy connection is created.
+ '''
+
+ self.idlequeue_thread = IdleQueueThread()
+ self.connection = MockConnectionClass()
+
+ self.idlequeue_thread.start()
+
+ def tearDown(self):
+ '''
+ IdleQueue thread is stopped.
+ '''
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ def open_stream(self, server_port):
+ '''
+ Method opening the XMPP connection. It returns when
+ is received from server.
+
+ :param server_port: tuple of (hostname, port) for where the client should
+ connect.
+
+ '''
+ self.client = client_nb.NonBlockingClient(
+ hostname=server_port[0],
+ port=server_port[1],
+ caller=self.connection,
+ idlequeue=self.idlequeue_thread.iq,
+ )
+ '''
+ NonBlockingClient instance with parameters from global variables and with
+ callbacks from dummy connection.
+ '''
+
+ self.client.connect(
+ on_connect=lambda *args: self.connection.on_connect(True, *args),
+ on_connect_failure=lambda *args: self.connection.on_connect(False, *args),
+ secure=False
+ )
+
+ print 'waiting for callback from client constructor'
+ self.connection.wait()
+
+ # if on_connect was called, client has to be connected and vice versa
+ if self.connection.connect_succeeded:
+ self.assert_(self.client.get_connect_type())
+ else:
+ self.assert_(not self.client.get_connect_type())
+
+ def client_auth(self, username, password, resource, sasl):
+ '''
+ Method authenticating connected client with supplied credentials. Returns
+ when authentication is over.
+ :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication
+
+ :todo: to check and be more specific about when it returns (bind, session..)
+ '''
+ self.client.auth(username, password, resource, sasl,
+ on_auth=self.connection.on_auth)
+
+ print 'waiting for authentication...'
+ self.connection.wait()
+
+ def do_disconnect(self):
+ '''
+ Does disconnecting of connected client. Returns when TCP connection is closed.
+ '''
+ #self.client.start_disconnect(None, on_disconnect=self.connection.set_event)
+ self.client.RegisterDisconnectHandler(self.connection.set_event)
+ self.client.disconnect()
+
+ print 'waiting for disconnecting...'
+ self.connection.wait()
+
+ def test_proper_connect_sasl(self):
+ '''
+ The ideal testcase - client is connected, authenticated with SASL and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+
+ # if client is not connected, lets raise the AssertionError
+ self.assert_(self.client.get_connect_type())
+ # (client.disconnect() is already called from NBClient._on_connected_failure
+ # so there's need to call it in this case
+
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='sasl')
+
+ self.do_disconnect()
+
+
+ def test_proper_connect_oldauth(self):
+ '''
+ The ideal testcase - client is connected, authenticated with old auth and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='old_auth')
+ self.do_disconnect()
+
+ def test_connect_to_nonexisting_host(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return nothing.
+ '''
+ self.open_stream(('fdsfsdf.fdsf.fss', 5222))
+ print 'nonexthost: %s' % self.client.get_connect_type()
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_to_wrong_port(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return some IP
+ but there shouldn't be XMPP server running on specified port.
+ '''
+ self.open_stream((xmpp_server_port[0], 31337))
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_with_wrong_creds(self):
+ '''
+ Connecting with invalid password.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], "wrong pass", credentials[2], sasl=1)
+ self.assert_(self.connection.auth is None)
+ self.do_disconnect()
+
+
+
+
+
+
+if __name__ == '__main__':
+
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestNonBlockingClient)
+ #suite = unittest.TestSuite()
+ #suite.addTest(TestNonBlockingClient('test_proper_connect_oldauth'))
+ #suite.addTest(TestNonBlockingClient('test_connect_to_nonexisting_host'))
+
+ unittest.TextTestRunner(verbosity=2).run(suite)
+
+
+
+
+
diff --git a/test/test_dispatcher_nb.py b/test/test_dispatcher_nb.py
index 45402f66b..9af6717b8 100644
--- a/test/test_dispatcher_nb.py
+++ b/test/test_dispatcher_nb.py
@@ -7,18 +7,18 @@ lib.setup_env()
from mock import Mock
from common.xmpp import dispatcher_nb
-from common.xmpp import auth
+from common.xmpp import auth_nb
class TestDispatcherNB(unittest.TestCase):
def test_unbound_namespace_prefix(self):
'''tests our handling of a message with an unbound namespace prefix'''
- d = dispatcher_nb.Dispatcher()
+ d = dispatcher_nb.XMPPDispatcher()
conn = Mock()
owner = Mock()
owner._caller = Mock()
- owner.defaultNamespace = auth.NS_CLIENT
+ owner.defaultNamespace = auth_nb.NS_CLIENT
owner.debug_flags = []
owner.Connection = conn
owner._component = False
diff --git a/test/test_nonblockingtcp.py b/test/test_nonblockingtcp.py
new file mode 100644
index 000000000..6c499ba89
--- /dev/null
+++ b/test/test_nonblockingtcp.py
@@ -0,0 +1,81 @@
+'''
+Unit test for NonBlockingTCP tranport.
+'''
+
+import unittest
+from xmpp_mocks import *
+
+import threading, sys, os.path, time
+
+gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
+
+sys.path.append(gajim_root + '/src/common/xmpp')
+sys.path.append(gajim_root + '/src/common')
+
+import transports_nb
+from client import *
+
+xmpp_server = ('gajim.org',5222)
+'''
+2-tuple - (XMPP server hostname, c2s port)
+Script will connect to the machine.
+'''
+
+import socket
+ips = socket.getaddrinfo(xmpp_server[0], xmpp_server[1], socket.AF_UNSPEC,socket.SOCK_STREAM)
+
+# change xmpp_server on real values
+ip = ips[0]
+
+class MockClient(IdleMock):
+ def __init__(self, idlequeue):
+ self.idlequeue=idlequeue
+ IdleMock.__init__(self)
+
+ def do_connect(self):
+ self.socket=transports_nb.NonBlockingTCP(
+ lambda(event_type, data): sys.stdout.write('raising event %s: %s' % (
+ event_type, data)), lambda: self.on_success(mode='SocketDisconnect'),
+ self.idlequeue, False, None)
+
+ self.socket.PlugIn(self)
+
+ self.socket.connect(conn_5tuple=ip,
+ on_connect=lambda: self.on_success(mode='TCPconnect'),
+ on_connect_failure=self.on_failure)
+ self.wait()
+
+ def do_disconnect(self):
+ self.socket.disconnect()
+ self.wait()
+
+ def on_failure(self, data):
+ print 'Error: %s' % data
+ self.set_event()
+
+ def on_success(self, mode, data=None):
+ if mode == "TCPconnect":
+ pass
+ if mode == "SocketDisconnect":
+ pass
+ self.set_event()
+
+class TestNonBlockingTCP(unittest.TestCase):
+ def setUp(self):
+ self.idlequeue_thread = IdleQueueThread()
+ self.idlequeue_thread.start()
+ self.client = MockClient(idlequeue=self.idlequeue_thread.iq)
+
+ def tearDown(self):
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ def testSth(self):
+ self.client.do_connect()
+ self.assert_(self.client.socket.state == 'CONNECTED')
+ self.client.do_disconnect()
+ self.assert_(self.client.socket.state == 'DISCONNECTED')
+
+if __name__ == '__main__':
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestNonBlockingTCP)
+ unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/test/test_resolver.py b/test/test_resolver.py
new file mode 100644
index 000000000..4cab122a0
--- /dev/null
+++ b/test/test_resolver.py
@@ -0,0 +1,95 @@
+import unittest
+
+import time
+
+import lib
+lib.setup_env()
+
+from common import resolver
+from gajim import GlibIdleQueue
+
+from mock import Mock, expectParams
+from mocks import *
+
+import gtk
+
+
+GMAIL_SRV_NAME = '_xmpp-client._tcp.gmail.com'
+NONSENSE_NAME = 'sfsdfsdfsdf.sdfs.fsd'
+JABBERCZ_TXT_NAME = '_xmppconnect.jabber.cz'
+JABBERCZ_SRV_NAME = '_xmpp-client._tcp.jabber.cz'
+
+TEST_LIST = [(GMAIL_SRV_NAME, 'srv', True),
+ (NONSENSE_NAME, 'srv', False),
+ (JABBERCZ_SRV_NAME, 'srv', True)]
+
+class TestResolver(unittest.TestCase):
+ def setUp(self):
+ self.iq = GlibIdleQueue()
+ self.reset()
+ self.resolver = None
+
+ def reset(self):
+ self.flag = False
+ self.expect_results = False
+ self.nslookup = False
+ self.resolver = None
+
+ def testLibAsyncNSResolver(self):
+ self.reset()
+ if not resolver.USE_LIBASYNCNS:
+ print 'testLibAsyncResolver: libasyncns-python not installed'
+ return
+ self.resolver = resolver.LibAsyncNSResolver()
+
+ for name, type, expect_results in TEST_LIST:
+ self.expect_results = expect_results
+ self.runLANSR(name, type)
+ self.flag = False
+
+ def runLANSR(self, name, type):
+ self.resolver.resolve(
+ host = name,
+ type = type,
+ on_ready = self.myonready)
+ while not self.flag:
+ time.sleep(1)
+ self.resolver.process()
+
+
+ def myonready(self, name, result_set):
+ print 'on_ready called ...'
+ print 'hostname: %s' % name
+ print 'result set: %s' % result_set
+ print 'res.resolved_hosts: %s' % self.resolver.resolved_hosts
+ if self.expect_results:
+ self.assert_(len(result_set) > 0)
+ else:
+ self.assert_(result_set == [])
+ self.flag = True
+ if self.nslookup: self._testNSLR()
+
+
+ def testNSLookupResolver(self):
+ self.reset()
+ self.nslookup = True
+ self.resolver = resolver.NSLookupResolver(self.iq)
+ self.test_list = TEST_LIST
+ self._testNSLR()
+ try:
+ gtk.main()
+ except KeyboardInterrupt:
+ print 'KeyboardInterrupt caught'
+
+ def _testNSLR(self):
+ if self.test_list == []:
+ gtk.main_quit()
+ return
+ name, type, self.expect_results = self.test_list.pop()
+ self.resolver.resolve(
+ host = name,
+ type = type,
+ on_ready = self.myonready)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/xmpp_mocks.py b/test/xmpp_mocks.py
new file mode 100644
index 000000000..891f0a235
--- /dev/null
+++ b/test/xmpp_mocks.py
@@ -0,0 +1,123 @@
+'''
+Module with dummy classes for unit testing of xmpp code (src/common/xmpp/*).
+'''
+
+import threading, time, os.path, sys
+
+import lib
+lib.setup_env()
+
+from mock import Mock
+
+gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
+
+sys.path.append(gajim_root + '/src/common/xmpp')
+
+'''
+Module with classes usable for XMPP related testing.
+'''
+
+import idlequeue
+from client import PlugIn
+
+idlequeue_interval = 0.2
+'''
+IdleQueue polling interval. 200ms is used in Gajim as default
+'''
+
+
+class IdleQueueThread(threading.Thread):
+ '''
+ Thread for regular processing of idlequeue.
+ '''
+ def __init__(self):
+ self.iq = idlequeue.IdleQueue()
+ self.stop = threading.Event()
+ '''
+ Event used to stopping the thread main loop.
+ '''
+
+ self.stop.clear()
+ threading.Thread.__init__(self)
+
+ def run(self):
+ while not self.stop.isSet():
+ self.iq.process()
+ time.sleep(idlequeue_interval)
+
+ def stop_thread(self):
+ self.stop.set()
+
+
+
+
+class IdleMock:
+ '''
+ Serves as template for testing objects that are normally controlled by GUI.
+ Allows to wait for asynchronous callbacks with wait() method.
+ '''
+ def __init__(self):
+ self.event = threading.Event()
+ '''
+ Event is used for waiting on callbacks.
+ '''
+ self.event.clear()
+
+
+ def wait(self):
+ '''
+ Waiting until some callback sets the event and clearing the event subsequently.
+ '''
+ self.event.wait()
+ self.event.clear()
+
+ def set_event(self):
+ self.event.set()
+
+
+class MockConnectionClass(IdleMock,Mock):
+ '''
+ Class simulating Connection class from src/common/connection.py
+
+ It is derived from Mock in order to avoid defining all methods
+ from real Connection that are called from NBClient or Dispatcher
+ ( _event_dispatcher for example)
+ '''
+
+ def __init__(self, *args):
+ self.connect_succeeded = True
+ IdleMock.__init__(self)
+ Mock.__init__(self, *args)
+
+ def on_connect(self, success, *args):
+ '''
+ Method called after connecting - after receiving
+ from server (NOT after TLS stream restart) or connect failure
+ '''
+
+ print 'on_connect - args:'
+ print ' success - %s' % success
+ for i in args:
+ print ' %s' % i
+ self.connect_succeeded = success
+ self.set_event()
+
+ def on_auth(self, con, auth):
+ '''
+ Method called after authentication is done regardless on the result.
+
+ :Parameters:
+ con : NonBlockingClient
+ reference to authenticated object
+ auth : string
+ type of authetication in case of success ('old_auth', 'sasl') or
+ None in case of auth failure
+ '''
+
+ #print 'on_auth - args:'
+ #print ' con: %s' % con
+ #print ' auth: %s' % auth
+ self.auth_connection = con
+ self.auth = auth
+ self.set_event()
+