#!/usr/bin/env python from __future__ import absolute_import, print_function import os, sys, time, types, subprocess, signal import dbus import logging import logging.handlers # cgitb is in python-misc and requires python-pkgutil and python-pydoc # It is very usefull for analyzing exceptions. # import cgitb # who am i myscript = os.path.basename(__file__) # Log formating class class flog: # priority strings to be used # with the __init__ function priorities = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warn': logging.WARNING, 'warning': logging.WARNING, 'error': logging.ERROR, 'critical': logging.CRITICAL, } def __init__(self,myscript,facility,priority): """ Initialize for logging :param myscript: The name of the python program script :param facility: The syslog facility, such as daemon or user :param priority: The minimum priority to be printed to the log :returns: Nothing :raises TBD: logging class errors. """ name_len = str(len(myscript)) self.myscript = myscript self.log = logging.getLogger(myscript) self.handler = logging.handlers.SysLogHandler(address=('/dev/log'),facility=facility) self.default_fmt = ' %(levelname)-9s %(name)-' + name_len + 's %(message)s' self.verbose_fmt1 = ' %(levelname)-9s %(name)-' + name_len + 's %(threadName)-14s ' self.verbose_fmt2 = ' %(message)s' formatter = logging.Formatter(self.default_fmt) self.handler.setFormatter(formatter) self.log.setLevel(self.priorities[priority]) # Minimum infolevel to log self.log.addHandler(self.handler) self.handler.createLock() def __default(self,func,*args): self.handler.acquire() formatter = logging.Formatter(self.default_fmt) self.handler.setFormatter(formatter) func(*args) self.handler.release() def setThreshold(self,threshold): """ Change the syslog priority threshold :param priority: Character string corresponding to the threshold """ self.handler.acquire() self.log.setLevel(self.priorities[threshold]) # Minimum infolevel to log self.handler.release() def critical(self,*args): """ Prints a variable argument list at critical priority :returns: logging result """ self.__default(self.log.critical,*args) def error(self,*args): """ Prints a variable argument list at error priority :returns: logging result """ self.__default(self.log.error,*args) def warning(self,*args): """ Prints a variable argument list at warning priority :returns: logging result """ self.__default(self.log.warning,*args) # Python has no notice level! def info(self,*args): """ Prints a variable argument list at info priority :returns: logging result """ self.__default(self.log.info,*args) def debug(self,*args): """ Prints a variable argument list at debug priority Printing debug includes function name and line number. :returns: logging result """ caller_frame = sys._getframe().f_back callerfunc = caller_frame.f_code.co_name + '@' + str(caller_frame.f_lineno); callerfunc = callerfunc.ljust(16) self.handler.acquire() log = logging.getLogger(self.myscript) formatter = logging.Formatter(self.verbose_fmt1+callerfunc+self.verbose_fmt2) self.handler.setFormatter(formatter) log.debug(*args) self.handler.release() # End of log handler ### ~bluezutils.py iface_base = 'org.bluez' iface_dev = '{}.Device1'.format(iface_base) iface_adapter = '{}.Adapter1'.format(iface_base) iface_props = 'org.freedesktop.DBus.Properties' global lg # Will this write to syslog????? class BTError(Exception): pass def get_bus(): bus = getattr(get_bus, 'cached_obj', None) if not bus: bus = get_bus.cached_obj = dbus.SystemBus() return bus def get_manager(): manager = getattr(get_manager, 'cached_obj', None) if not manager: try: manager = get_manager.cached_obj = dbus.Interface( get_bus().get_object(iface_base, '/'), 'org.freedesktop.DBus.ObjectManager' ) except dbus.exceptions.DBusException as e: s="" try: s = e.__dict__["_dbus_error_name"] except KeyError: pass unk = 'org.freedesktop.DBus.Error.ServiceUnknown' cexit = 'org.freedesktop.DBus.Error.Spawn.ChildExited' if (s == unk) or (s == cexit): msg = 'Is bluetoothd running? Bluetooth tree missing from DBUS' lg.error(msg) print(msg) exit(1) else: raise return manager def prop_get(obj, k, iface=None): if iface is None: iface = obj.dbus_interface return obj.Get(iface, k, dbus_interface=iface_props) def prop_set(obj, k, v, iface=None): if iface is None: iface = obj.dbus_interface return obj.Set(iface, k, v, dbus_interface=iface_props) msg='' def find_adapter(pattern=None): try: adapter = find_adapter_in_objects(get_manager().GetManagedObjects(), pattern) # DBusException # Original code: # template = "An exception of type {0} occurred. Arguments:\n{1!r}" # message = template.format(type(ex).__name__, ex.args) # print message # dbus.exceptions.DBusException: # org.freedesktop.DBus.Error.AccessDenied: except dbus.exceptions.DBusException as e: s="" try: s = e.__dict__["_dbus_error_name"] except KeyError: pass if "org.freedesktop.DBus.Error.AccessDenied" == s: rot="You do not have sufficient privilege to run bt-pan" print(rot) lg.error(rot) exit(1) else: raise return adapter def find_adapter_in_objects(objects, pattern=None): bus, obj = get_bus(), None for path, ifaces in objects.iteritems(): adapter = ifaces.get(iface_adapter) if adapter is None: continue if not pattern or pattern == adapter['Address'] or path.endswith(pattern): obj = bus.get_object(iface_base, path) yield dbus.Interface(obj, iface_adapter) if obj is None: msg = 'Bluetooth adapter not found' lg.error(msg) raise BTError(msg) def find_device(device_address, adapter_pattern=None): return find_device_in_objects(get_manager().GetManagedObjects(), device_address, adapter_pattern) def find_device_in_objects(objects, device_address, adapter_pattern=None): bus = get_bus() path_prefix = '' if adapter_pattern: if not isinstance(adapter_pattern, types.StringTypes): adapter = adapter_pattern else: adapter = find_adapter_in_objects(objects, adapter_pattern) path_prefix = adapter.object_path for path, ifaces in objects.iteritems(): device = ifaces.get(iface_dev) if device is None: continue if device['Address'] == device_address and path.startswith(path_prefix): obj = bus.get_object(iface_base, path) return dbus.Interface(obj, iface_dev) msg = 'Bluetooth device not found' lg.error(msg) raise BTError(msg) pidPath = "" def writePidFile(device): global pidPath pid = str(os.getpid()) pidPath = '/run/bt-pan.' + device + '.pid' f = open(pidPath, 'w') f.write(pid) f.close() def hexdump(string): return ":".join("{:02x}".format(ord(c)) for c in string) ### bt-pan def main(args=None): import argparse global lg # Set up logging initially info and above lg = flog(myscript,'daemon','info') # cgitb.enable(format='text') parser = argparse.ArgumentParser( description='BlueZ bluetooth PAN network server/client.') parser.add_argument('-i', '--device', metavar='local-addr/pattern', help='Local device address/pattern to use (if not default).') parser.add_argument('-a', '--device-all', action='store_true', help='Use all local hci devices, not just default one.' ' Only valid with "server" mode, mutually exclusive with --device option.') parser.add_argument('-u', '--uuid', metavar='uuid_or_shortcut', default='nap', help='Service UUID to use. Can be either full UUID' ' or one of the shortcuts: gn, panu, nap. Default: %(default)s.') parser.add_argument('--systemd', action='store_true', help='Use systemd service' ' notification/watchdog mechanisms in daemon modes, if available.') parser.add_argument('--debug', action='store_true', help='Verbose operation mode.') cmds = parser.add_subparsers( dest='call', title='Supported operations (have their own suboptions as well)' ) cmd = cmds.add_parser('server', help='Run infinitely as a NAP network server.') cmd.add_argument('iface_name', help='Bridge interface name to which each link will be added by bluez.' ' It must be created and configured before starting the server.') cmd = cmds.add_parser('client', help='Connect to a PAN network.') cmd.add_argument('remote_addr', help='Remote device address to connect to.') cmd.add_argument('-d', '--disconnect', action='store_true', help='Instead of connecting (default action), disconnect' ' (if connected) and exit. Overrides all other options for this command.') cmd.add_argument('-w', '--wait', action='store_true', help='Go into an endless wait-loop after connection, terminating it on exit.') cmd.add_argument('-c', '--if-not-connected', action='store_true', help='Dont raise error if connection is already established.') cmd.add_argument('-r', '--reconnect', action='store_true', help='Force reconnection if some connection is already established.') opts = parser.parse_args() if opts.debug: lg.setThreshold('debug') if not opts.device_all: devs = [next(iter(find_adapter(opts.device)))] else: if opts.call != 'server': parser.error('--device-all option is only valid with "server" mode.') devs = list(find_adapter()) devs = dict((prop_get(dev, 'Address'), dev) for dev in devs) for dev_addr, dev in devs.viewitems(): prop_set(dev, 'Powered', True) lg.debug('Using local device (addr: %s): %s', dev_addr, dev.object_path) wait_iter_noop = 3600 if opts.systemd: from systemd import daemon def wait_iter(): if not wait_iter.sd_ready: daemon.notify('READY=1') daemon.notify('STATUS=Running in {} mode...'.format(opts.call)) wait_iter.sd_ready = True time.sleep(wait_iter.timeout) if wait_iter.sd_wdt: daemon.notify('WATCHDOG=1') wd_pid, wd_usec = (os.environ.get(k) for k in ['WATCHDOG_PID', 'WATCHDOG_USEC']) if wd_pid and wd_pid.isdigit() and int(wd_pid) == os.getpid(): wd_interval = float(wd_usec) / 2e6 # half of interval in seconds assert wd_interval > 0, wd_interval else: wd_interval = None if wd_interval: lg.debug('Initializing systemd watchdog pinger with interval: %ss', wd_interval) wait_iter.sd_wdt, wait_iter.timeout = True, min(wd_interval, wait_iter_noop) else: wait_iter.sd_wdt, wait_iter.timeout = False, wait_iter_noop wait_iter.sd_ready = False else: wait_iter = lambda: time.sleep(wait_iter_noop) signal.signal(signal.SIGTERM, lambda sig,frm: sys.exit(0)) if opts.call == 'server': inm = opts.iface_name brctl = subprocess.Popen( ['brctl', 'show', inm], stdout=open(os.devnull, 'wb'), stderr=subprocess.PIPE ) brctl_stderr = brctl.stderr.read() writePidFile(opts.iface_name) if brctl.wait() or brctl_stderr: lg.error('brctl check failed for interface (missing?): {}'.format(inm)) lg.error('Bridge interface must be added and configured before starting server, e.g. with:') lg.error(' brctl addbr {}'.format(inm)) lg.error(' brctl setfd {} 0'.format(inm)) lg.error(' brctl stp {} off'.format(inm)) lg.error(' ip addr add 10.101.225.84/24 dev {}'.format(inm)) lg.error(' ip link set {} up'.format(inm)) return 1 servers = list() try: for dev_addr, dev in devs.viewitems(): server = dbus.Interface(dev, 'org.bluez.NetworkServer1') server.Unregister(opts.uuid) # in case already registered server.Register(opts.uuid, opts.iface_name) servers.append(server) lg.debug( 'Registered uuid %r with' ' bridge/dev: %s / %s', opts.uuid, opts.iface_name, dev_addr ) while True: wait_iter() except KeyboardInterrupt: pass finally: if servers: for server in servers: server.Unregister(opts.uuid) lg.debug('Unregistered server uuids') elif opts.call == 'client': dev_remote = find_device(opts.remote_addr, devs.values()[0]) lg.debug( 'Using remote device (addr: %s): %s', prop_get(dev_remote, 'Address'), dev_remote.object_path ) try: dev_remote.ConnectProfile(opts.uuid) except: pass # no idea why it fails sometimes, but still creates dbus interface net = dbus.Interface(dev_remote, 'org.bluez.Network1') if opts.disconnect: try: net.Disconnect() except dbus.exceptions.DBusException as err: if err.get_dbus_name() != 'org.bluez.Error.Failed': raise connected = prop_get(net, 'Connected') if connected: raise lg.debug( 'Disconnected from network' ' (dev_remote: %s, addr: %s) uuid %r, by explicit command', dev_remote.object_path, prop_get(dev_remote, 'Address'), opts.uuid ) return for n in xrange(2): try: iface = net.Connect(opts.uuid) except dbus.exceptions.DBusException as err: if err.get_dbus_name() != 'org.bluez.Error.Failed': raise connected = prop_get(net, 'Connected') if not connected: raise if opts.reconnect: lg.debug( 'Detected pre-established connection' ' (iface: %s), reconnecting', prop_get(net, 'Interface') ) net.Disconnect() continue if not opts.if_not_connected: raise else: break lg.debug( 'Connected to network (dev_remote: %s, addr: %s) uuid %r with iface: %s', dev_remote.object_path, prop_get(dev_remote, 'Address'), opts.uuid, iface ) if opts.wait: try: while True: wait_iter() except KeyboardInterrupt: pass finally: net.Disconnect() lg.debug('Disconnected from network') else: raise ValueError(opts.call) global pidPath try: os.remove(pidPath) except OSError: pass lg.debug('Finished') if __name__ == '__main__': sys.exit(main())