2006-05-26 16:27:42 +02:00
|
|
|
##
|
|
|
|
## Copyright (C) 2006 Gajim Team
|
|
|
|
##
|
|
|
|
## Contributors for this file:
|
|
|
|
## - Yann Le Boulanger <asterix@lagaule.org>
|
|
|
|
## - Nikos Kouremenos <nkour@jabber.org>
|
|
|
|
## - Dimitur Kirov <dkirov@gmail.com>
|
|
|
|
## - Travis Shirk <travis@pobox.com>
|
2006-07-27 22:36:21 +02:00
|
|
|
## - Stefan Bethge <stefan@lanpartei.de>
|
2006-05-26 16:27:42 +02:00
|
|
|
##
|
|
|
|
## This program is free software; you can redistribute it and/or modify
|
|
|
|
## it under the terms of the GNU General Public License as published
|
|
|
|
## by the Free Software Foundation; version 2 only.
|
|
|
|
##
|
|
|
|
## This program is distributed in the hope that it will be useful,
|
|
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
## GNU General Public License for more details.
|
|
|
|
##
|
|
|
|
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
import base64
|
|
|
|
import sha
|
|
|
|
import socket
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from calendar import timegm
|
|
|
|
|
2006-06-28 01:09:21 +02:00
|
|
|
#import socks5
|
2006-05-26 16:27:42 +02:00
|
|
|
import common.xmpp
|
|
|
|
|
|
|
|
from common import GnuPG
|
|
|
|
from common import helpers
|
|
|
|
from common import gajim
|
2006-09-18 00:57:41 +02:00
|
|
|
from common.zeroconf import zeroconf
|
2006-05-26 16:27:42 +02:00
|
|
|
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
|
|
|
'invisible']
|
|
|
|
# kind of events we can wait for an answer
|
|
|
|
VCARD_PUBLISHED = 'vcard_published'
|
|
|
|
VCARD_ARRIVED = 'vcard_arrived'
|
|
|
|
AGENT_REMOVED = 'agent_removed'
|
|
|
|
HAS_IDLE = True
|
|
|
|
try:
|
|
|
|
import common.idle as idle # when we launch gajim from sources
|
|
|
|
except:
|
|
|
|
try:
|
|
|
|
import idle # when Gajim is installed
|
|
|
|
except:
|
|
|
|
gajim.log.debug(_('Unable to load idle module'))
|
|
|
|
HAS_IDLE = False
|
|
|
|
|
|
|
|
class ConnectionVcard:
|
|
|
|
def __init__(self):
|
|
|
|
self.vcard_sha = None
|
|
|
|
self.vcard_shas = {} # sha of contacts
|
|
|
|
self.room_jids = [] # list of gc jids so that vcard are saved in a folder
|
|
|
|
|
|
|
|
def add_sha(self, p, send_caps = True):
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
2006-05-26 16:27:42 +02:00
|
|
|
c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE)
|
|
|
|
if self.vcard_sha is not None:
|
|
|
|
c.setTagData('photo', self.vcard_sha)
|
|
|
|
if send_caps:
|
|
|
|
return self.add_caps(p)
|
|
|
|
return p
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
|
|
|
pass
|
2006-05-26 16:27:42 +02:00
|
|
|
|
|
|
|
def add_caps(self, p):
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
|
|
|
# advertise our capabilities in presence stanza (jep-0115)
|
2006-05-26 16:27:42 +02:00
|
|
|
c = p.setTag('c', namespace = common.xmpp.NS_CAPS)
|
|
|
|
c.setAttr('node', 'http://gajim.org/caps')
|
|
|
|
c.setAttr('ext', 'ftrans')
|
|
|
|
c.setAttr('ver', gajim.version)
|
|
|
|
return p
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
|
|
|
pass
|
2006-05-26 16:27:42 +02:00
|
|
|
|
|
|
|
def node_to_dict(self, node):
|
|
|
|
dict = {}
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
2006-05-26 16:27:42 +02:00
|
|
|
for info in node.getChildren():
|
|
|
|
name = info.getName()
|
|
|
|
if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
|
|
|
|
if not dict.has_key(name):
|
|
|
|
dict[name] = []
|
|
|
|
entry = {}
|
|
|
|
for c in info.getChildren():
|
|
|
|
entry[c.getName()] = c.getData()
|
|
|
|
dict[name].append(entry)
|
|
|
|
elif info.getChildren() == []:
|
|
|
|
dict[name] = info.getData()
|
|
|
|
else:
|
|
|
|
dict[name] = {}
|
|
|
|
for c in info.getChildren():
|
|
|
|
dict[name][c.getName()] = c.getData()
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
2006-05-26 16:27:42 +02:00
|
|
|
return dict
|
|
|
|
|
|
|
|
def save_vcard_to_hd(self, full_jid, card):
|
|
|
|
jid, nick = gajim.get_room_and_nick_from_fjid(full_jid)
|
|
|
|
puny_jid = helpers.sanitize_filename(jid)
|
|
|
|
path = os.path.join(gajim.VCARD_PATH, puny_jid)
|
|
|
|
if jid in self.room_jids or os.path.isdir(path):
|
|
|
|
# remove room_jid file if needed
|
|
|
|
if os.path.isfile(path):
|
|
|
|
os.remove(path)
|
|
|
|
# create folder if needed
|
|
|
|
if not os.path.isdir(path):
|
|
|
|
os.mkdir(path, 0700)
|
|
|
|
puny_nick = helpers.sanitize_filename(nick)
|
|
|
|
path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
|
|
|
|
else:
|
|
|
|
path_to_file = path
|
|
|
|
fil = open(path_to_file, 'w')
|
|
|
|
fil.write(str(card))
|
|
|
|
fil.close()
|
|
|
|
|
|
|
|
def get_cached_vcard(self, fjid, is_fake_jid = False):
|
|
|
|
'''return the vcard as a dict
|
|
|
|
return {} if vcard was too old
|
|
|
|
return None if we don't have cached vcard'''
|
|
|
|
jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
|
|
|
|
puny_jid = helpers.sanitize_filename(jid)
|
|
|
|
if is_fake_jid:
|
|
|
|
puny_nick = helpers.sanitize_filename(nick)
|
|
|
|
path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
|
|
|
|
else:
|
|
|
|
path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid)
|
|
|
|
if not os.path.isfile(path_to_file):
|
|
|
|
return None
|
|
|
|
# We have the vcard cached
|
|
|
|
f = open(path_to_file)
|
|
|
|
c = f.read()
|
|
|
|
f.close()
|
|
|
|
card = common.xmpp.Node(node = c)
|
|
|
|
vcard = self.node_to_dict(card)
|
|
|
|
if vcard.has_key('PHOTO'):
|
|
|
|
if not isinstance(vcard['PHOTO'], dict):
|
|
|
|
del vcard['PHOTO']
|
|
|
|
elif vcard['PHOTO'].has_key('SHA'):
|
|
|
|
cached_sha = vcard['PHOTO']['SHA']
|
|
|
|
if self.vcard_shas.has_key(jid) and self.vcard_shas[jid] != \
|
|
|
|
cached_sha:
|
|
|
|
# user change his vcard so don't use the cached one
|
|
|
|
return {}
|
|
|
|
vcard['jid'] = jid
|
|
|
|
vcard['resource'] = gajim.get_resource_from_jid(fjid)
|
|
|
|
return vcard
|
|
|
|
|
|
|
|
def request_vcard(self, jid = None, is_fake_jid = False):
|
|
|
|
'''request the VCARD. If is_fake_jid is True, it means we request a vcard
|
|
|
|
to a fake jid, like in private messages in groupchat'''
|
|
|
|
if not self.connection:
|
|
|
|
return
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
2006-05-26 16:27:42 +02:00
|
|
|
iq = common.xmpp.Iq(typ = 'get')
|
|
|
|
if jid:
|
|
|
|
iq.setTo(jid)
|
|
|
|
iq.setTag(common.xmpp.NS_VCARD + ' vCard')
|
|
|
|
|
|
|
|
id = self.connection.getAnID()
|
|
|
|
iq.setID(id)
|
|
|
|
self.awaiting_answers[id] = (VCARD_ARRIVED, jid)
|
|
|
|
if is_fake_jid:
|
|
|
|
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
|
|
|
|
if not room_jid in self.room_jids:
|
|
|
|
self.room_jids.append(room_jid)
|
|
|
|
self.connection.send(iq)
|
|
|
|
#('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
|
|
|
pass
|
|
|
|
|
2006-05-26 16:27:42 +02:00
|
|
|
def send_vcard(self, vcard):
|
|
|
|
if not self.connection:
|
|
|
|
return
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
2006-05-26 16:27:42 +02:00
|
|
|
iq = common.xmpp.Iq(typ = 'set')
|
|
|
|
iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard')
|
|
|
|
for i in vcard:
|
|
|
|
if i == 'jid':
|
|
|
|
continue
|
|
|
|
if isinstance(vcard[i], dict):
|
|
|
|
iq3 = iq2.addChild(i)
|
|
|
|
for j in vcard[i]:
|
|
|
|
iq3.addChild(j).setData(vcard[i][j])
|
|
|
|
elif type(vcard[i]) == type([]):
|
|
|
|
for j in vcard[i]:
|
|
|
|
iq3 = iq2.addChild(i)
|
|
|
|
for k in j:
|
|
|
|
iq3.addChild(k).setData(j[k])
|
|
|
|
else:
|
|
|
|
iq2.addChild(i).setData(vcard[i])
|
|
|
|
|
|
|
|
id = self.connection.getAnID()
|
|
|
|
iq.setID(id)
|
|
|
|
self.connection.send(iq)
|
|
|
|
|
|
|
|
# Add the sha of the avatar
|
|
|
|
if vcard.has_key('PHOTO') and isinstance(vcard['PHOTO'], dict) and \
|
|
|
|
vcard['PHOTO'].has_key('BINVAL'):
|
|
|
|
photo = vcard['PHOTO']['BINVAL']
|
|
|
|
photo_decoded = base64.decodestring(photo)
|
|
|
|
our_jid = gajim.get_jid_from_account(self.name)
|
|
|
|
gajim.interface.save_avatar_files(our_jid, photo_decoded)
|
|
|
|
avatar_sha = sha.sha(photo_decoded).hexdigest()
|
|
|
|
iq2.getTag('PHOTO').setTagData('SHA', avatar_sha)
|
|
|
|
|
|
|
|
self.awaiting_answers[id] = (VCARD_PUBLISHED, iq2)
|
2006-05-29 21:57:39 +02:00
|
|
|
'''
|
|
|
|
pass
|
|
|
|
|
|
|
|
class ConnectionHandlersZeroconf(ConnectionVcard):
|
2006-05-26 16:27:42 +02:00
|
|
|
def __init__(self):
|
|
|
|
ConnectionVcard.__init__(self)
|
|
|
|
# List of IDs we are waiting answers for {id: (type_of_request, data), }
|
|
|
|
self.awaiting_answers = {}
|
|
|
|
# List of IDs that will produce a timeout is answer doesn't arrive
|
|
|
|
# {time_of_the_timeout: (id, message to send to gui), }
|
|
|
|
self.awaiting_timeouts = {}
|
|
|
|
# keep the jids we auto added (transports contacts) to not send the
|
|
|
|
# SUBSCRIBED event to gui
|
|
|
|
self.automatically_added = []
|
|
|
|
try:
|
|
|
|
idle.init()
|
|
|
|
except:
|
|
|
|
HAS_IDLE = False
|
2006-09-18 00:57:41 +02:00
|
|
|
def _messageCB(self, ip, con, msg):
|
|
|
|
'''Called when we receive a message'''
|
|
|
|
msgtxt = msg.getBody()
|
|
|
|
mtype = msg.getType()
|
|
|
|
subject = msg.getSubject() # if not there, it's None
|
|
|
|
tim = msg.getTimestamp()
|
|
|
|
tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
|
|
|
|
tim = time.localtime(timegm(tim))
|
|
|
|
frm = helpers.get_full_jid_from_iq(msg)
|
|
|
|
if frm == 'none':
|
|
|
|
for key in self.zeroconf.contacts:
|
|
|
|
if ip == self.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
|
|
|
|
frm = key
|
|
|
|
jid = helpers.get_jid_from_iq(msg)
|
|
|
|
print 'jid', jid
|
|
|
|
no_log_for = gajim.config.get_per('accounts', self.name,
|
|
|
|
'no_log_for').split()
|
|
|
|
encrypted = False
|
|
|
|
chatstate = None
|
|
|
|
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
|
|
|
|
decmsg = ''
|
|
|
|
# invitations
|
|
|
|
invite = None
|
|
|
|
if not encTag:
|
|
|
|
invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
|
|
|
|
if invite and not invite.getTag('invite'):
|
|
|
|
invite = None
|
|
|
|
delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None
|
|
|
|
msg_id = None
|
|
|
|
composing_jep = None
|
|
|
|
# FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED
|
|
|
|
# invitation
|
|
|
|
# stanza (MUC JEP) remove in 2007, as we do not do NOT RECOMMENDED
|
|
|
|
xtags = msg.getTags('x')
|
|
|
|
# chatstates - look for chatstate tags in a message if not delayed
|
|
|
|
if not delayed:
|
|
|
|
composing_jep = False
|
|
|
|
children = msg.getChildren()
|
|
|
|
for child in children:
|
|
|
|
if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
|
|
|
|
chatstate = child.getName()
|
|
|
|
composing_jep = 'JEP-0085'
|
|
|
|
break
|
|
|
|
# No JEP-0085 support, fallback to JEP-0022
|
|
|
|
if not chatstate:
|
|
|
|
chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT)
|
|
|
|
if chatstate_child:
|
|
|
|
chatstate = 'active'
|
|
|
|
composing_jep = 'JEP-0022'
|
|
|
|
if not msgtxt and chatstate_child.getTag('composing'):
|
|
|
|
chatstate = 'composing'
|
|
|
|
# JEP-0172 User Nickname
|
|
|
|
user_nick = msg.getTagData('nick')
|
|
|
|
if not user_nick:
|
|
|
|
user_nick = ''
|
|
|
|
|
|
|
|
if encTag and GnuPG.USE_GPG:
|
|
|
|
#decrypt
|
|
|
|
encmsg = encTag.getData()
|
|
|
|
|
|
|
|
keyID = gajim.config.get_per('accounts', self.name, 'keyid')
|
|
|
|
if keyID:
|
|
|
|
decmsg = self.gpg.decrypt(encmsg, keyID)
|
|
|
|
if decmsg:
|
|
|
|
msgtxt = decmsg
|
|
|
|
encrypted = True
|
|
|
|
if mtype == 'error':
|
|
|
|
error_msg = msg.getError()
|
|
|
|
if not error_msg:
|
|
|
|
error_msg = msgtxt
|
|
|
|
msgtxt = None
|
|
|
|
if self.name not in no_log_for:
|
|
|
|
gajim.logger.write('error', frm, error_msg, tim = tim,
|
|
|
|
subject = subject)
|
|
|
|
self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
|
|
|
|
tim))
|
|
|
|
elif mtype == 'chat': # it's type 'chat'
|
|
|
|
if not msg.getTag('body') and chatstate is None: #no <body>
|
|
|
|
return
|
|
|
|
if msg.getTag('body') and self.name not in no_log_for and jid not in\
|
|
|
|
no_log_for and msgtxt:
|
|
|
|
msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
|
|
|
|
subject = subject)
|
|
|
|
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
|
|
|
|
chatstate, msg_id, composing_jep, user_nick))
|
|
|
|
else: # it's single message
|
|
|
|
if self.name not in no_log_for and jid not in no_log_for and msgtxt:
|
|
|
|
gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
|
|
|
|
subject = subject)
|
|
|
|
if invite is not None:
|
|
|
|
item = invite.getTag('invite')
|
|
|
|
jid_from = item.getAttr('from')
|
|
|
|
if jid_from == None:
|
|
|
|
jid_from = frm
|
|
|
|
reason = item.getTagData('reason')
|
|
|
|
item = invite.getTag('password')
|
|
|
|
password = invite.getTagData('password')
|
|
|
|
self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
|
|
|
|
else:
|
|
|
|
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
|
|
|
|
subject, chatstate, msg_id, composing_jep, user_nick))
|
|
|
|
# END messageCB
|
2006-05-31 01:13:36 +02:00
|
|
|
'''
|
2006-05-26 16:27:42 +02:00
|
|
|
def build_http_auth_answer(self, iq_obj, answer):
|
|
|
|
if answer == 'yes':
|
|
|
|
iq = iq_obj.buildReply('result')
|
|
|
|
elif answer == 'no':
|
|
|
|
iq = iq_obj.buildReply('error')
|
|
|
|
iq.setError('not-authorized', 401)
|
|
|
|
self.connection.send(iq)
|
2006-05-31 01:13:36 +02:00
|
|
|
'''
|
2006-05-26 16:27:42 +02:00
|
|
|
|
|
|
|
def parse_data_form(self, node):
|
|
|
|
dic = {}
|
|
|
|
tag = node.getTag('title')
|
|
|
|
if tag:
|
|
|
|
dic['title'] = tag.getData()
|
|
|
|
tag = node.getTag('instructions')
|
|
|
|
if tag:
|
|
|
|
dic['instructions'] = tag.getData()
|
|
|
|
i = 0
|
|
|
|
for child in node.getChildren():
|
|
|
|
if child.getName() != 'field':
|
|
|
|
continue
|
|
|
|
var = child.getAttr('var')
|
|
|
|
ctype = child.getAttr('type')
|
|
|
|
label = child.getAttr('label')
|
|
|
|
if not var and ctype != 'fixed': # We must have var if type != fixed
|
|
|
|
continue
|
|
|
|
dic[i] = {}
|
|
|
|
if var:
|
|
|
|
dic[i]['var'] = var
|
|
|
|
if ctype:
|
|
|
|
dic[i]['type'] = ctype
|
|
|
|
if label:
|
|
|
|
dic[i]['label'] = label
|
|
|
|
tags = child.getTags('value')
|
|
|
|
if len(tags):
|
|
|
|
dic[i]['values'] = []
|
|
|
|
for tag in tags:
|
|
|
|
data = tag.getData()
|
|
|
|
if ctype == 'boolean':
|
|
|
|
if data in ('yes', 'true', 'assent', '1'):
|
|
|
|
data = True
|
|
|
|
else:
|
|
|
|
data = False
|
|
|
|
dic[i]['values'].append(data)
|
|
|
|
tag = child.getTag('desc')
|
|
|
|
if tag:
|
|
|
|
dic[i]['desc'] = tag.getData()
|
|
|
|
option_tags = child.getTags('option')
|
|
|
|
if len(option_tags):
|
|
|
|
dic[i]['options'] = {}
|
|
|
|
j = 0
|
|
|
|
for option_tag in option_tags:
|
|
|
|
dic[i]['options'][j] = {}
|
|
|
|
label = option_tag.getAttr('label')
|
|
|
|
tags = option_tag.getTags('value')
|
|
|
|
dic[i]['options'][j]['values'] = []
|
|
|
|
for tag in tags:
|
|
|
|
dic[i]['options'][j]['values'].append(tag.getData())
|
|
|
|
if not label:
|
|
|
|
label = dic[i]['options'][j]['values'][0]
|
|
|
|
dic[i]['options'][j]['label'] = label
|
|
|
|
j += 1
|
|
|
|
if not dic[i].has_key('values'):
|
|
|
|
dic[i]['values'] = [dic[i]['options'][0]['values'][0]]
|
|
|
|
i += 1
|
|
|
|
return dic
|
2006-09-17 16:45:58 +02:00
|
|
|
|
|
|
|
def store_metacontacts(self, tags):
|
|
|
|
''' fake empty method '''
|
|
|
|
# serverside metacontacts are not supported with zeroconf
|
|
|
|
# (there is no server)
|
|
|
|
pass
|
2006-06-13 23:19:39 +02:00
|
|
|
def remove_transfers_for_contact(self, contact):
|
|
|
|
''' stop all active transfer for contact '''
|
|
|
|
'''for file_props in self.files_props.values():
|
|
|
|
if self.is_transfer_stoped(file_props):
|
|
|
|
continue
|
|
|
|
receiver_jid = unicode(file_props['receiver']).split('/')[0]
|
|
|
|
if contact.jid == receiver_jid:
|
|
|
|
file_props['error'] = -5
|
|
|
|
self.remove_transfer(file_props)
|
|
|
|
self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props))
|
|
|
|
sender_jid = unicode(file_props['sender']).split('/')[0]
|
|
|
|
if contact.jid == sender_jid:
|
|
|
|
file_props['error'] = -3
|
|
|
|
self.remove_transfer(file_props)
|
|
|
|
'''
|
|
|
|
pass
|
2006-05-31 01:13:36 +02:00
|
|
|
|
2006-06-13 23:19:39 +02:00
|
|
|
def remove_all_transfers(self):
|
|
|
|
''' stops and removes all active connections from the socks5 pool '''
|
|
|
|
'''
|
|
|
|
for file_props in self.files_props.values():
|
|
|
|
self.remove_transfer(file_props, remove_from_list = False)
|
|
|
|
del(self.files_props)
|
|
|
|
self.files_props = {}
|
|
|
|
'''
|
|
|
|
pass
|
|
|
|
|
|
|
|
def remove_transfer(self, file_props, remove_from_list = True):
|
|
|
|
'''
|
|
|
|
if file_props is None:
|
|
|
|
return
|
|
|
|
self.disconnect_transfer(file_props)
|
|
|
|
sid = file_props['sid']
|
|
|
|
gajim.socks5queue.remove_file_props(self.name, sid)
|
|
|
|
|
|
|
|
if remove_from_list:
|
|
|
|
if self.files_props.has_key('sid'):
|
|
|
|
del(self.files_props['sid'])
|
|
|
|
'''
|
|
|
|
pass
|
|
|
|
|