| 1 | ## common/connection.py |
|---|
| 2 | ## |
|---|
| 3 | ## Contributors for this file: |
|---|
| 4 | ## - Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 5 | ## - Nikos Kouremenos <nkour@jabber.org> |
|---|
| 6 | ## - Dimitur Kirov <dkirov@gmail.com> |
|---|
| 7 | ## - Travis Shirk <travis@pobox.com> |
|---|
| 8 | ## |
|---|
| 9 | ## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 10 | ## Vincent Hanquez <tab@snarc.org> |
|---|
| 11 | ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 12 | ## Vincent Hanquez <tab@snarc.org> |
|---|
| 13 | ## Nikos Kouremenos <nkour@jabber.org> |
|---|
| 14 | ## Dimitur Kirov <dkirov@gmail.com> |
|---|
| 15 | ## Travis Shirk <travis@pobox.com> |
|---|
| 16 | ## Norman Rasmussen <norman@rasmussen.co.za> |
|---|
| 17 | ## |
|---|
| 18 | ## This program is free software; you can redistribute it and/or modify |
|---|
| 19 | ## it under the terms of the GNU General Public License as published |
|---|
| 20 | ## by the Free Software Foundation; version 2 only. |
|---|
| 21 | ## |
|---|
| 22 | ## This program is distributed in the hope that it will be useful, |
|---|
| 23 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 24 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 25 | ## GNU General Public License for more details. |
|---|
| 26 | ## |
|---|
| 27 | |
|---|
| 28 | # kind of events we can wait for an answer |
|---|
| 29 | VCARD_PUBLISHED = 'vcard_published' |
|---|
| 30 | VCARD_ARRIVED = 'vcard_arrived' |
|---|
| 31 | |
|---|
| 32 | import sys |
|---|
| 33 | import sha |
|---|
| 34 | import os |
|---|
| 35 | import time |
|---|
| 36 | import sre |
|---|
| 37 | import traceback |
|---|
| 38 | import threading |
|---|
| 39 | import select |
|---|
| 40 | import socket |
|---|
| 41 | import random |
|---|
| 42 | random.seed() |
|---|
| 43 | import signal |
|---|
| 44 | import base64 |
|---|
| 45 | if os.name != 'nt': |
|---|
| 46 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) |
|---|
| 47 | |
|---|
| 48 | from calendar import timegm |
|---|
| 49 | |
|---|
| 50 | import common.xmpp |
|---|
| 51 | |
|---|
| 52 | from common import helpers |
|---|
| 53 | from common import gajim |
|---|
| 54 | from common import GnuPG |
|---|
| 55 | import socks5 |
|---|
| 56 | USE_GPG = GnuPG.USE_GPG |
|---|
| 57 | |
|---|
| 58 | from common import i18n |
|---|
| 59 | _ = i18n._ |
|---|
| 60 | |
|---|
| 61 | # determine which DNS resolution library is available |
|---|
| 62 | HAS_DNSPYTHON = False |
|---|
| 63 | HAS_PYDNS = False |
|---|
| 64 | try: |
|---|
| 65 | import dns.resolver # http://dnspython.org/ |
|---|
| 66 | HAS_DNSPYTHON = True |
|---|
| 67 | except ImportError: |
|---|
| 68 | try: |
|---|
| 69 | import DNS # http://pydns.sf.net/ |
|---|
| 70 | HAS_PYDNS = True |
|---|
| 71 | except ImportError: |
|---|
| 72 | gajim.log.debug("Could not load one of the supported DNS libraries (dnspython or pydns). SRV records will not be queried and you may need to set custom hostname/port for some servers to be accessible.") |
|---|
| 73 | |
|---|
| 74 | |
|---|
| 75 | STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', |
|---|
| 76 | 'invisible'] |
|---|
| 77 | |
|---|
| 78 | distro_info = { |
|---|
| 79 | 'Arch Linux': '/etc/arch-release', |
|---|
| 80 | 'Aurox Linux': '/etc/aurox-release', |
|---|
| 81 | 'Conectiva Linux': '/etc/conectiva-release', |
|---|
| 82 | 'Debian GNU/Linux': '/etc/debian_release', |
|---|
| 83 | 'Debian GNU/Linux': '/etc/debian_version', |
|---|
| 84 | 'Fedora Linux': '/etc/fedora-release', |
|---|
| 85 | 'Gentoo Linux': '/etc/gentoo-release', |
|---|
| 86 | 'Linux from Scratch': '/etc/lfs-release', |
|---|
| 87 | 'Mandrake Linux': '/etc/mandrake-release', |
|---|
| 88 | 'Slackware Linux': '/etc/slackware-release', |
|---|
| 89 | 'Slackware Linux': '/etc/slackware-version', |
|---|
| 90 | 'Solaris/Sparc': '/etc/release', |
|---|
| 91 | 'Source Mage': '/etc/sourcemage_version', |
|---|
| 92 | 'SUSE Linux': '/etc/SuSE-release', |
|---|
| 93 | 'Sun JDS': '/etc/sun-release', |
|---|
| 94 | 'PLD Linux': '/etc/pld-release', |
|---|
| 95 | 'Yellow Dog Linux': '/etc/yellowdog-release', |
|---|
| 96 | # many distros use the /etc/redhat-release for compatibility |
|---|
| 97 | # so Redhat is the last |
|---|
| 98 | 'Redhat Linux': '/etc/redhat-release' |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | def get_os_info(): |
|---|
| 102 | if os.name == 'nt': |
|---|
| 103 | ver = os.sys.getwindowsversion() |
|---|
| 104 | ver_format = ver[3], ver[0], ver[1] |
|---|
| 105 | win_version = { |
|---|
| 106 | (1, 4, 0): '95', |
|---|
| 107 | (1, 4, 10): '98', |
|---|
| 108 | (1, 4, 90): 'ME', |
|---|
| 109 | (2, 4, 0): 'NT', |
|---|
| 110 | (2, 5, 0): '2000', |
|---|
| 111 | (2, 5, 1): 'XP', |
|---|
| 112 | (2, 5, 2): '2003' |
|---|
| 113 | } |
|---|
| 114 | if win_version.has_key(ver_format): |
|---|
| 115 | return 'Windows' + ' ' + win_version[ver_format] |
|---|
| 116 | else: |
|---|
| 117 | return 'Windows' |
|---|
| 118 | elif os.name == 'posix': |
|---|
| 119 | executable = 'lsb_release' |
|---|
| 120 | params = ' --id --codename --release --short' |
|---|
| 121 | full_path_to_executable = helpers.is_in_path(executable, return_abs_path = True) |
|---|
| 122 | if full_path_to_executable: |
|---|
| 123 | command = executable + params |
|---|
| 124 | child_stdin, child_stdout = os.popen2(command) |
|---|
| 125 | output = helpers.temp_failure_retry(child_stdout.readline).strip() |
|---|
| 126 | child_stdout.close() |
|---|
| 127 | child_stdin.close() |
|---|
| 128 | # some distros put n/a in places so remove them |
|---|
| 129 | pattern = sre.compile(r' n/a', sre.IGNORECASE) |
|---|
| 130 | output = sre.sub(pattern, '', output) |
|---|
| 131 | return output |
|---|
| 132 | |
|---|
| 133 | # lsb_release executable not available, so parse files |
|---|
| 134 | for distro_name in distro_info: |
|---|
| 135 | path_to_file = distro_info[distro_name] |
|---|
| 136 | if os.path.exists(path_to_file): |
|---|
| 137 | fd = open(path_to_file) |
|---|
| 138 | text = fd.readline().strip() #get only first line |
|---|
| 139 | fd.close() |
|---|
| 140 | if path_to_file.endswith('version'): |
|---|
| 141 | # sourcemage_version has all the info we need |
|---|
| 142 | if not os.path.basename(path_to_file).startswith('sourcemage'): |
|---|
| 143 | text = distro_name + ' ' + text |
|---|
| 144 | elif path_to_file.endswith('aurox-release'): |
|---|
| 145 | # file doesn't have version |
|---|
| 146 | text = distro_name |
|---|
| 147 | elif path_to_file.endswith('lfs-release'): # file just has version |
|---|
| 148 | text = distro_name + ' ' + text |
|---|
| 149 | return text |
|---|
| 150 | |
|---|
| 151 | # our last chance, ask uname and strip it |
|---|
| 152 | uname_output = helpers.get_output_of_command('uname -a | cut -d" " -f1,3') |
|---|
| 153 | if uname_output is not None: |
|---|
| 154 | return uname_output[0] # only first line |
|---|
| 155 | return 'N/A' |
|---|
| 156 | |
|---|
| 157 | class Connection: |
|---|
| 158 | """Connection class""" |
|---|
| 159 | def __init__(self, name): |
|---|
| 160 | self.name = name |
|---|
| 161 | self.connected = 0 # offline |
|---|
| 162 | self.connection = None # xmpppy instance |
|---|
| 163 | self.gpg = None |
|---|
| 164 | self.vcard_sha = None |
|---|
| 165 | self.vcard_shas = {} # sha of contacts |
|---|
| 166 | self.status = '' |
|---|
| 167 | self.old_show = '' |
|---|
| 168 | # holds the actual hostname to which we are connected |
|---|
| 169 | self.connected_hostname = None |
|---|
| 170 | self.time_to_reconnect = None |
|---|
| 171 | self.new_account_info = None |
|---|
| 172 | self.bookmarks = [] |
|---|
| 173 | self.on_purpose = False |
|---|
| 174 | self.last_io = time.time() |
|---|
| 175 | self.to_be_sent = [] |
|---|
| 176 | self.last_sent = [] |
|---|
| 177 | self.files_props = {} |
|---|
| 178 | self.last_history_line = {} |
|---|
| 179 | self.password = gajim.config.get_per('accounts', name, 'password') |
|---|
| 180 | self.server_resource = gajim.config.get_per('accounts', name, 'resource') |
|---|
| 181 | self.privacy_rules_supported = False |
|---|
| 182 | # Do we continue connection when we get roster (send presence,get vcard...) |
|---|
| 183 | self.continue_connect_info = None |
|---|
| 184 | # List of IDs we are waiting answers for {id: (type_of_request, data), } |
|---|
| 185 | self.awaiting_answers = {} |
|---|
| 186 | # List of IDs that will produce a timeout is answer doesn't arrive |
|---|
| 187 | # {time_of_the_timeout: (id, message to send to gui), } |
|---|
| 188 | self.awaiting_timeouts = {} |
|---|
| 189 | if USE_GPG: |
|---|
| 190 | self.gpg = GnuPG.GnuPG() |
|---|
| 191 | gajim.config.set('usegpg', True) |
|---|
| 192 | else: |
|---|
| 193 | gajim.config.set('usegpg', False) |
|---|
| 194 | self.retrycount = 0 |
|---|
| 195 | # END __init__ |
|---|
| 196 | |
|---|
| 197 | def get_full_jid(self, iq_obj): |
|---|
| 198 | '''return the full jid (with resource) from an iq as unicode''' |
|---|
| 199 | return helpers.parse_jid(str(iq_obj.getFrom())) |
|---|
| 200 | |
|---|
| 201 | def get_jid(self, iq_obj): |
|---|
| 202 | '''return the jid (without resource) from an iq as unicode''' |
|---|
| 203 | jid = self.get_full_jid(iq_obj) |
|---|
| 204 | return gajim.get_jid_without_resource(jid) |
|---|
| 205 | |
|---|
| 206 | def put_event(self, ev): |
|---|
| 207 | if gajim.events_for_ui.has_key(self.name): |
|---|
| 208 | gajim.events_for_ui[self.name].append(ev) |
|---|
| 209 | |
|---|
| 210 | def dispatch(self, event, data): |
|---|
| 211 | '''always passes account name as first param''' |
|---|
| 212 | gajim.mutex_events_for_ui.lock(self.put_event, [event, data]) |
|---|
| 213 | gajim.mutex_events_for_ui.unlock() |
|---|
| 214 | |
|---|
| 215 | def add_sha(self, p): |
|---|
| 216 | c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE) |
|---|
| 217 | if self.vcard_sha is not None: |
|---|
| 218 | c.setTagData('photo', self.vcard_sha) |
|---|
| 219 | return p |
|---|
| 220 | |
|---|
| 221 | # this is in features.py but it is blocking |
|---|
| 222 | def _discover(self, ns, jid, node = None): |
|---|
| 223 | if not self.connection: |
|---|
| 224 | return |
|---|
| 225 | iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = ns) |
|---|
| 226 | if node: |
|---|
| 227 | iq.setQuerynode(node) |
|---|
| 228 | self.to_be_sent.append(iq) |
|---|
| 229 | |
|---|
| 230 | def discoverItems(self, jid, node = None): |
|---|
| 231 | '''According to JEP-0030: jid is mandatory, |
|---|
| 232 | name, node, action is optional.''' |
|---|
| 233 | self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node) |
|---|
| 234 | |
|---|
| 235 | def discoverInfo(self, jid, node = None): |
|---|
| 236 | '''According to JEP-0030: |
|---|
| 237 | For identity: category, type is mandatory, name is optional. |
|---|
| 238 | For feature: var is mandatory''' |
|---|
| 239 | self._discover(common.xmpp.NS_DISCO_INFO, jid, node) |
|---|
| 240 | |
|---|
| 241 | def node_to_dict(self, node): |
|---|
| 242 | dict = {} |
|---|
| 243 | for info in node.getChildren(): |
|---|
| 244 | name = info.getName() |
|---|
| 245 | if name in ('ADR', 'TEL', 'EMAIL'): # we can have several |
|---|
| 246 | if not dict.has_key(name): |
|---|
| 247 | dict[name] = [] |
|---|
| 248 | entry = {} |
|---|
| 249 | for c in info.getChildren(): |
|---|
| 250 | entry[c.getName()] = c.getData() |
|---|
| 251 | dict[name].append(entry) |
|---|
| 252 | elif info.getChildren() == []: |
|---|
| 253 | dict[name] = info.getData() |
|---|
| 254 | else: |
|---|
| 255 | dict[name] = {} |
|---|
| 256 | for c in info.getChildren(): |
|---|
| 257 | dict[name][c.getName()] = c.getData() |
|---|
| 258 | return dict |
|---|
| 259 | |
|---|
| 260 | def _vCardCB(self, con, vc): |
|---|
| 261 | """Called when we receive a vCard |
|---|
| 262 | Parse the vCard and send it to plugins""" |
|---|
| 263 | if not vc.getTag('vCard'): |
|---|
| 264 | return |
|---|
| 265 | frm_iq = vc.getFrom() |
|---|
| 266 | our_jid = gajim.get_jid_from_account(self.name) |
|---|
| 267 | resource = '' |
|---|
| 268 | if frm_iq: |
|---|
| 269 | who = self.get_full_jid(vc) |
|---|
| 270 | frm, resource = gajim.get_room_and_nick_from_fjid(who) |
|---|
| 271 | else: |
|---|
| 272 | frm = our_jid |
|---|
| 273 | if vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD: |
|---|
| 274 | card = vc.getChildren()[0] |
|---|
| 275 | vcard = self.node_to_dict(card) |
|---|
| 276 | if vcard.has_key('PHOTO') and isinstance(vcard['PHOTO'], dict) and \ |
|---|
| 277 | vcard['PHOTO'].has_key('BINVAL'): |
|---|
| 278 | photo = vcard['PHOTO']['BINVAL'] |
|---|
| 279 | photo_decoded = base64.decodestring(photo) |
|---|
| 280 | avatar_sha = sha.sha(photo_decoded).hexdigest() |
|---|
| 281 | else: |
|---|
| 282 | avatar_sha = '' |
|---|
| 283 | |
|---|
| 284 | if avatar_sha: |
|---|
| 285 | card.getTag('PHOTO').setTagData('SHA', avatar_sha) |
|---|
| 286 | if frm != our_jid: |
|---|
| 287 | if avatar_sha: |
|---|
| 288 | self.vcard_shas[frm] = avatar_sha |
|---|
| 289 | elif self.vcard_shas.has_key(frm): |
|---|
| 290 | del self.vcard_shas[frm] |
|---|
| 291 | |
|---|
| 292 | # Save it to file |
|---|
| 293 | path_to_file = os.path.join(gajim.VCARDPATH, frm) |
|---|
| 294 | fil = open(path_to_file, 'w') |
|---|
| 295 | fil.write(str(card)) |
|---|
| 296 | fil.close() |
|---|
| 297 | |
|---|
| 298 | vcard['jid'] = frm |
|---|
| 299 | vcard['resource'] = resource |
|---|
| 300 | if frm == our_jid: |
|---|
| 301 | self.dispatch('MYVCARD', vcard) |
|---|
| 302 | # we re-send our presence with sha if has changed and if we are |
|---|
| 303 | # not invisible |
|---|
| 304 | if self.vcard_sha == avatar_sha: |
|---|
| 305 | return |
|---|
| 306 | self.vcard_sha = avatar_sha |
|---|
| 307 | if STATUS_LIST[self.connected] == 'invisible': |
|---|
| 308 | return |
|---|
| 309 | sshow = helpers.get_xmpp_show(STATUS_LIST[self.connected]) |
|---|
| 310 | prio = unicode(gajim.config.get_per('accounts', self.name, |
|---|
| 311 | 'priority')) |
|---|
| 312 | p = common.xmpp.Presence(typ = None, priority = prio, show = sshow, |
|---|
| 313 | status = self.status) |
|---|
| 314 | p = self.add_sha(p) |
|---|
| 315 | self.to_be_sent.append(p) |
|---|
| 316 | else: |
|---|
| 317 | self.dispatch('VCARD', vcard) |
|---|
| 318 | |
|---|
| 319 | |
|---|
| 320 | def _messageCB(self, con, msg): |
|---|
| 321 | """Called when we receive a message""" |
|---|
| 322 | msgtxt = msg.getBody() |
|---|
| 323 | mtype = msg.getType() |
|---|
| 324 | subject = msg.getSubject() # if not there, it's None |
|---|
| 325 | tim = msg.getTimestamp() |
|---|
| 326 | tim = time.strptime(tim, '%Y%m%dT%H:%M:%S') |
|---|
| 327 | tim = time.localtime(timegm(tim)) |
|---|
| 328 | frm = self.get_full_jid(msg) |
|---|
| 329 | jid = self.get_jid(msg) |
|---|
| 330 | no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for') |
|---|
| 331 | encrypted = False |
|---|
| 332 | chatstate = None |
|---|
| 333 | xtags = msg.getTags('x') |
|---|
| 334 | encTag = None |
|---|
| 335 | decmsg = '' |
|---|
| 336 | invite = None |
|---|
| 337 | for xtag in xtags: |
|---|
| 338 | if xtag.getNamespace() == common.xmpp.NS_ENCRYPTED: |
|---|
| 339 | encTag = xtag |
|---|
| 340 | break |
|---|
| 341 | #invitations |
|---|
| 342 | elif xtag.getNamespace() == common.xmpp.NS_MUC_USER and \ |
|---|
| 343 | xtag.getTag('invite'): |
|---|
| 344 | invite = xtag |
|---|
| 345 | # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED |
|---|
| 346 | # invitation |
|---|
| 347 | # stanza (MUC JEP) remove in 2007, as we do not do NOT RECOMMENDED |
|---|
| 348 | elif xtag.getNamespace() == common.xmpp.NS_CONFERENCE: |
|---|
| 349 | room_jid = xtag.getAttr('jid') |
|---|
| 350 | self.dispatch('GC_INVITATION', (room_jid, frm, '', None)) |
|---|
| 351 | return |
|---|
| 352 | # chatstates - look for chatstate tags in a message |
|---|
| 353 | children = msg.getChildren() |
|---|
| 354 | for child in children: |
|---|
| 355 | if child.getNamespace() == 'http://jabber.org/protocol/chatstates': |
|---|
| 356 | chatstate = child.getName() |
|---|
| 357 | break |
|---|
| 358 | |
|---|
| 359 | if encTag and USE_GPG: |
|---|
| 360 | #decrypt |
|---|
| 361 | encmsg = encTag.getData() |
|---|
| 362 | |
|---|
| 363 | keyID = gajim.config.get_per('accounts', self.name, 'keyid') |
|---|
| 364 | if keyID: |
|---|
| 365 | decmsg = self.gpg.decrypt(encmsg, keyID) |
|---|
| 366 | if decmsg: |
|---|
| 367 | msgtxt = decmsg |
|---|
| 368 | encrypted = True |
|---|
| 369 | if mtype == 'error': |
|---|
| 370 | self.dispatch('MSGERROR', (frm, msg.getErrorCode(), msg.getError(), |
|---|
| 371 | msgtxt, tim)) |
|---|
| 372 | elif mtype == 'groupchat': |
|---|
| 373 | if subject: |
|---|
| 374 | self.dispatch('GC_SUBJECT', (frm, subject)) |
|---|
| 375 | else: |
|---|
| 376 | if not msg.getTag('body'): #no <body> |
|---|
| 377 | return |
|---|
| 378 | self.dispatch('GC_MSG', (frm, msgtxt, tim)) |
|---|
| 379 | if self.name not in no_log_for and not\ |
|---|
| 380 | int(float(time.mktime(tim))) <= self.last_history_line[jid]: |
|---|
| 381 | gajim.logger.write('gc_msg', frm, msgtxt, tim = tim) |
|---|
| 382 | elif mtype == 'chat': # it's type 'chat' |
|---|
| 383 | if not msg.getTag('body') and chatstate is None: #no <body> |
|---|
| 384 | return |
|---|
| 385 | if msg.getTag('body') and self.name not in no_log_for and jid not in\ |
|---|
| 386 | no_log_for: |
|---|
| 387 | gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim, subject = subject) |
|---|
| 388 | self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject, |
|---|
| 389 | chatstate)) |
|---|
| 390 | else: # it's single message |
|---|
| 391 | if self.name not in no_log_for and jid not in no_log_for: |
|---|
| 392 | gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim, subject = subject) |
|---|
| 393 | if invite is not None: |
|---|
| 394 | item = invite.getTag('invite') |
|---|
| 395 | jid_from = item.getAttr('from') |
|---|
| 396 | reason = item.getTagData('reason') |
|---|
| 397 | item = invite.getTag('password') |
|---|
| 398 | password = invite.getTagData('password') |
|---|
| 399 | self.dispatch('GC_INVITATION',(frm, jid_from, reason, password)) |
|---|
| 400 | else: |
|---|
| 401 | self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal', |
|---|
| 402 | subject, None)) |
|---|
| 403 | # END messageCB |
|---|
| 404 | |
|---|
| 405 | def _presenceCB(self, con, prs): |
|---|
| 406 | """Called when we receive a presence""" |
|---|
| 407 | ptype = prs.getType() |
|---|
| 408 | if ptype == 'available': |
|---|
| 409 | ptype = None |
|---|
| 410 | gajim.log.debug('PresenceCB: %s' % ptype) |
|---|
| 411 | is_gc = False # is it a GC presence ? |
|---|
| 412 | sigTag = None |
|---|
| 413 | avatar_sha = None |
|---|
| 414 | xtags = prs.getTags('x') |
|---|
| 415 | for x in xtags: |
|---|
| 416 | if x.getNamespace().startswith(common.xmpp.NS_MUC): |
|---|
| 417 | is_gc = True |
|---|
| 418 | if x.getNamespace() == common.xmpp.NS_SIGNED: |
|---|
| 419 | sigTag = x |
|---|
| 420 | if x.getNamespace() == common.xmpp.NS_VCARD_UPDATE: |
|---|
| 421 | avatar_sha = x.getTagData('photo') |
|---|
| 422 | |
|---|
| 423 | who = self.get_full_jid(prs) |
|---|
| 424 | jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) |
|---|
| 425 | no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for') |
|---|
| 426 | status = prs.getStatus() |
|---|
| 427 | show = prs.getShow() |
|---|
| 428 | if not show in STATUS_LIST: |
|---|
| 429 | show = '' # We ignore unknown show |
|---|
| 430 | if not ptype and not show: |
|---|
| 431 | show = 'online' |
|---|
| 432 | elif ptype == 'unavailable': |
|---|
| 433 | show = 'offline' |
|---|
| 434 | |
|---|
| 435 | prio = prs.getPriority() |
|---|
| 436 | try: |
|---|
| 437 | prio = int(prio) |
|---|
| 438 | except: |
|---|
| 439 | prio = 0 |
|---|
| 440 | keyID = '' |
|---|
| 441 | if sigTag and USE_GPG: |
|---|
| 442 | #verify |
|---|
| 443 | sigmsg = sigTag.getData() |
|---|
| 444 | keyID = self.gpg.verify(status, sigmsg) |
|---|
| 445 | |
|---|
| 446 | if is_gc: |
|---|
| 447 | if ptype == 'error': |
|---|
| 448 | errmsg = prs.getError() |
|---|
| 449 | errcode = prs.getErrorCode() |
|---|
| 450 | if errcode == '502': # Internal Timeout: |
|---|
| 451 | self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, |
|---|
| 452 | prio, keyID)) |
|---|
| 453 | elif errcode == '401': # password required to join |
|---|
| 454 | self.dispatch('ERROR', (_('Unable to join room'), |
|---|
| 455 | _('A password is required to join this room.'))) |
|---|
| 456 | elif errcode == '403': # we are banned |
|---|
| 457 | self.dispatch('ERROR', (_('Unable to join room'), |
|---|
| 458 | _('You are banned from this room.'))) |
|---|
| 459 | elif errcode == '404': # room does not exist |
|---|
| 460 | self.dispatch('ERROR', (_('Unable to join room'), |
|---|
| 461 | _('Such room does not exist.'))) |
|---|
| 462 | elif errcode == '405': |
|---|
| 463 | self.dispatch('ERROR', (_('Unable to join room'), |
|---|
| 464 | _('Room creation is restricted.'))) |
|---|
| 465 | elif errcode == '406': |
|---|
| 466 | self.dispatch('ERROR', (_('Unable to join room'), |
|---|
| 467 | _('Your registered nickname must be used.'))) |
|---|
| 468 | elif errcode == '407': |
|---|
| 469 | self.dispatch('ERROR', (_('Unable to join room'), |
|---|
| 470 | _('You are not in the members list.'))) |
|---|
| 471 | elif errcode == '409': # nick conflict |
|---|
| 472 | # the jid_from in this case is FAKE JID: room_jid/nick |
|---|
| 473 | # resource holds the bad nick so propose a new one |
|---|
| 474 | proposed_nickname = resource + \ |
|---|
| 475 | gajim.config.get('gc_proposed_nick_char') |
|---|
| 476 | room_jid = gajim.get_room_from_fjid(who) |
|---|
| 477 | self.dispatch('ASK_NEW_NICK', (room_jid, _('Unable to join room'), |
|---|
| 478 | _('Your desired nickname is in use or registered by another occupant.\nPlease specify another nickname below:'), proposed_nickname)) |
|---|
| 479 | else: # print in the window the error |
|---|
| 480 | self.dispatch('ERROR_ANSWER', ('', jid_stripped, |
|---|
| 481 | errmsg, errcode)) |
|---|
| 482 | if not ptype or ptype == 'unavailable': |
|---|
| 483 | if gajim.config.get('log_contact_status_changes') and self.name\ |
|---|
| 484 | not in no_log_for and jid_stripped not in no_log_for: |
|---|
| 485 | gajim.logger.write('gcstatus', who, status, show) |
|---|
| 486 | self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource, |
|---|
| 487 | prs.getRole(), prs.getAffiliation(), prs.getJid(), |
|---|
| 488 | prs.getReason(), prs.getActor(), prs.getStatusCode(), |
|---|
| 489 | prs.getNewNick())) |
|---|
| 490 | return |
|---|
| 491 | |
|---|
| 492 | if ptype == 'subscribe': |
|---|
| 493 | gajim.log.debug('subscribe request from %s' % who) |
|---|
| 494 | if gajim.config.get('alwaysauth') or who.find("@") <= 0: |
|---|
| 495 | if self.connection: |
|---|
| 496 | p = common.xmpp.Presence(who, 'subscribed') |
|---|
| 497 | p = self.add_sha(p) |
|---|
| 498 | self.to_be_sent.append(p) |
|---|
| 499 | if who.find("@") <= 0: |
|---|
| 500 | self.dispatch('NOTIFY', |
|---|
| 501 | (jid_stripped, 'offline', 'offline', resource, prio, keyID)) |
|---|
| 502 | else: |
|---|
| 503 | if not status: |
|---|
| 504 | status = _('I would like to add you to my roster.') |
|---|
| 505 | self.dispatch('SUBSCRIBE', (who, status)) |
|---|
| 506 | elif ptype == 'subscribed': |
|---|
| 507 | self.dispatch('SUBSCRIBED', (jid_stripped, resource)) |
|---|
| 508 | # BE CAREFUL: no con.updateRosterItem() in a callback |
|---|
| 509 | gajim.log.debug(_('we are now subscribed to %s') % who) |
|---|
| 510 | elif ptype == 'unsubscribe': |
|---|
| 511 | gajim.log.debug(_('unsubscribe request from %s') % who) |
|---|
| 512 | elif ptype == 'unsubscribed': |
|---|
| 513 | gajim.log.debug(_('we are now unsubscribed from %s') % who) |
|---|
| 514 | self.dispatch('UNSUBSCRIBED', jid_stripped) |
|---|
| 515 | elif ptype == 'error': |
|---|
| 516 | errmsg = prs.getError() |
|---|
| 517 | errcode = prs.getErrorCode() |
|---|
| 518 | if errcode == '502': # Internal Timeout: |
|---|
| 519 | self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource, |
|---|
| 520 | prio, keyID)) |
|---|
| 521 | else: # print in the window the error |
|---|
| 522 | self.dispatch('ERROR_ANSWER', ('', jid_stripped, |
|---|
| 523 | errmsg, errcode)) |
|---|
| 524 | |
|---|
| 525 | if avatar_sha: |
|---|
| 526 | if self.vcard_shas.has_key(jid_stripped): |
|---|
| 527 | if avatar_sha != self.vcard_shas[jid_stripped]: |
|---|
| 528 | # avatar has been updated |
|---|
| 529 | self.request_vcard(jid_stripped) |
|---|
| 530 | else: |
|---|
| 531 | self.vcard_shas[jid_stripped] = avatar_sha |
|---|
| 532 | if not ptype or ptype == 'unavailable': |
|---|
| 533 | if gajim.config.get('log_contact_status_changes') and self.name\ |
|---|
| 534 | not in no_log_for and jid_stripped not in no_log_for: |
|---|
| 535 | gajim.logger.write('status', jid_stripped, status, show) |
|---|
| 536 | self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio, |
|---|
| 537 | keyID)) |
|---|
| 538 | # END presenceCB |
|---|
| 539 | |
|---|
| 540 | def _disconnectedCB(self): |
|---|
| 541 | """Called when we are disconnected""" |
|---|
| 542 | gajim.log.debug('disconnectedCB') |
|---|
| 543 | if not self.connection: |
|---|
| 544 | return |
|---|
| 545 | self.connected = 0 |
|---|
| 546 | self.dispatch('STATUS', 'offline') |
|---|
| 547 | self.connection = None |
|---|
| 548 | if not self.on_purpose: |
|---|
| 549 | self.dispatch('ERROR', |
|---|
| 550 | (_('Connection with account "%s" has been lost') % self.name, |
|---|
| 551 | _('To continue sending and receiving messages, you will need to reconnect.'))) |
|---|
| 552 | self.on_purpose = False |
|---|
| 553 | |
|---|
| 554 | # END disconenctedCB |
|---|
| 555 | |
|---|
| 556 | def _reconnect(self): |
|---|
| 557 | # Do not try to reco while we are already trying |
|---|
| 558 | self.time_to_reconnect = None |
|---|
| 559 | t = threading.Thread(target=self._reconnect2) |
|---|
| 560 | t.start() |
|---|
| 561 | |
|---|
| 562 | def _reconnect2(self): |
|---|
| 563 | gajim.log.debug('reconnect') |
|---|
| 564 | self.retrycount += 1 |
|---|
| 565 | signed = self.get_signed_msg(self.status) |
|---|
| 566 | self.connect_and_init(self.old_show, self.status, signed) |
|---|
| 567 | if self.connected < 2: #connection failed |
|---|
| 568 | if self.retrycount > 10: |
|---|
| 569 | self.connected = 0 |
|---|
| 570 | self.dispatch('STATUS', 'offline') |
|---|
| 571 | self.dispatch('ERROR', |
|---|
| 572 | (_('Connection with account "%s" has been lost') % self.name, |
|---|
| 573 | _('To continue sending and receiving messages, you will need to reconnect.'))) |
|---|
| 574 | self.retrycount = 0 |
|---|
| 575 | |
|---|