# Copyright (C) 2006 Stefan Bethge # Copyright (C) 2006 Philipp Hörist # # 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 logging import select import re from gajim.common.i18n import _ from gajim.common.zeroconf.zeroconf import Constant log = logging.getLogger('gajim.c.z.zeroconf_bonjour') try: from pybonjour import kDNSServiceErr_NoError from pybonjour import kDNSServiceErr_ServiceNotRunning from pybonjour import kDNSServiceErr_NameConflict from pybonjour import kDNSServiceInterfaceIndexAny from pybonjour import kDNSServiceType_TXT from pybonjour import kDNSServiceFlagsAdd from pybonjour import kDNSServiceFlagsNoAutoRename from pybonjour import BonjourError from pybonjour import TXTRecord from pybonjour import DNSServiceUpdateRecord from pybonjour import DNSServiceResolve from pybonjour import DNSServiceProcessResult from pybonjour import DNSServiceGetAddrInfo from pybonjour import DNSServiceQueryRecord from pybonjour import DNSServiceBrowse from pybonjour import DNSServiceRegister except ImportError: pass resolve_timeout = 1 class Zeroconf: def __init__(self, new_service_cb, remove_service_cb, name_conflict_cb, _disconnected_cb, error_cb, name, host, port): self.stype = '_presence._tcp' self.port = port # listening port that gets announced self.username = name self.host = host self.txt = {} # service data self.name = None self.connected = False self.announced = False # XXX these CBs should be set to None when we destroy the object # (go offline), because they create a circular reference self._new_service_cb = new_service_cb self._remove_service_cb = remove_service_cb self._name_conflict_cb = name_conflict_cb self._error_cb = error_cb self._service_sdref = None self._browse_sdref = None self._contacts = {} # all current local contacts with data self._invalid_self_contact = {} self._resolved_hosts = {} self._resolved = [] self._queried = [] def _browse_callback(self, _sdref, flags, interface, error_code, service_name, regtype, reply_domain): log.debug('Found service %s in domain %s on %i(type: %s).', service_name, reply_domain, interface, regtype) if not self.connected: return if error_code != kDNSServiceErr_NoError: log.debug('Error in browse_callback: %s', str(error_code)) return if not flags & kDNSServiceFlagsAdd: self._remove_service_callback(service_name) return try: # asynchronous resolving resolve_sdref = None resolve_sdref = DNSServiceResolve( 0, interface, service_name, regtype, reply_domain, self._service_resolved_callback) while not self._resolved: ready = select.select([resolve_sdref], [], [], resolve_timeout) if resolve_sdref not in ready[0]: log.info('Resolve timed out') break DNSServiceProcessResult(resolve_sdref) else: self._resolved.pop() except BonjourError as error: log.info('Error when resolving DNS: %s', error) finally: if resolve_sdref: resolve_sdref.close() def _remove_service_callback(self, name): log.info('Service %s disappeared.', name) if not self.connected: return if name != self.name: for key in list(self._contacts.keys()): if self._contacts[key][Constant.NAME] == name: del self._contacts[key] self._remove_service_cb(key) return @staticmethod def txt_array_to_dict(txt): if not isinstance(txt, TXTRecord): txt = TXTRecord.parse(txt) return dict((v[0], v[1]) for v in txt) @staticmethod def _parse_name(fullname): log.debug('Parse name: %s', fullname) # TODO: do proper decoding... escaping = {r'\.': '.', r'\032': ' ', r'\064': '@', } # Split on '.' but do not split on '\.' result = re.split(r'(?