98
98
class AvahiService(object):
99
"""An Avahi (Zeroconf) service.
101
100
interface: integer; avahi.IF_UNSPEC or an interface index.
102
101
Used to optionally bind to the specified interface.
103
name: string; Example: 'Mandos'
104
type: string; Example: '_mandos._tcp'.
105
See <http://www.dns-sd.org/ServiceTypes.html>
106
port: integer; what port to announce
107
TXT: list of strings; TXT record for the service
108
domain: string; Domain to publish on, default to .local if empty.
109
host: string; Host to publish records for, default is localhost
110
max_renames: integer; maximum number of renames
111
rename_count: integer; counter so we only rename after collisions
112
a sensible number of times
102
name = string; Example: "Mandos"
103
type = string; Example: "_mandos._tcp".
104
See <http://www.dns-sd.org/ServiceTypes.html>
105
port = integer; what port to announce
106
TXT = list of strings; TXT record for the service
107
domain = string; Domain to publish on, default to .local if empty.
108
host = string; Host to publish records for, default to localhost
110
max_renames = integer; maximum number of renames
111
rename_count = integer; counter so we only rename after collisions
112
a sensible number of times
114
114
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
115
115
type = None, port = None, TXT = None, domain = "",
116
116
host = "", max_renames = 12):
117
"""An Avahi (Zeroconf) service. """
117
118
self.interface = interface
132
133
u" retries, exiting.", rename_count)
133
134
raise AvahiServiceError("Too many renames")
134
135
name = server.GetAlternativeServiceName(name)
135
logger.error(u"Changing name to %r ...", name)
136
logger.notice(u"Changing name to %r ...", name)
138
139
self.rename_count += 1
220
221
interval = property(lambda self: self._interval,
222
223
del _set_interval
223
def __init__(self, name = None, stop_hook=None, config={}):
224
"""Note: the 'checker' key in 'config' sets the
225
'checker_command' attribute and *not* the 'checker'
224
def __init__(self, name=None, stop_hook=None, fingerprint=None,
225
secret=None, secfile=None, fqdn=None, timeout=None,
226
interval=-1, checker=None):
227
"""Note: the 'checker' argument sets the 'checker_command'
228
attribute and not the 'checker' attribute.."""
228
230
logger.debug(u"Creating client %r", self.name)
229
# Uppercase and remove spaces from fingerprint for later
230
# comparison purposes with return value from the fingerprint()
232
self.fingerprint = config["fingerprint"].upper()\
231
# Uppercase and remove spaces from fingerprint
232
# for later comparison purposes with return value of
233
# the fingerprint() function
234
self.fingerprint = fingerprint.upper().replace(u" ", u"")
234
235
logger.debug(u" Fingerprint: %s", self.fingerprint)
235
if "secret" in config:
236
self.secret = config["secret"].decode(u"base64")
237
elif "secfile" in config:
238
sf = open(config["secfile"])
237
self.secret = secret.decode(u"base64")
239
240
self.secret = sf.read()
242
243
raise TypeError(u"No secret or secfile for client %s"
244
self.fqdn = config.get("fqdn", "")
245
246
self.created = datetime.datetime.now()
246
247
self.last_checked_ok = None
247
self.timeout = string_to_delta(config["timeout"])
248
self.interval = string_to_delta(config["interval"])
248
self.timeout = string_to_delta(timeout)
249
self.interval = string_to_delta(interval)
249
250
self.stop_hook = stop_hook
250
251
self.checker = None
251
252
self.checker_initiator_tag = None
252
253
self.stop_initiator_tag = None
253
254
self.checker_callback_tag = None
254
self.check_command = config["checker"]
255
self.check_command = checker
256
257
"""Start this client's checker and timeout hooks"""
257
258
# Schedule a new checker to be started an 'interval' from now,
296
297
self.checker = None
297
298
if os.WIFEXITED(condition) \
298
299
and (os.WEXITSTATUS(condition) == 0):
299
logger.info(u"Checker for %(name)s succeeded",
300
logger.debug(u"Checker for %(name)s succeeded",
301
302
self.last_checked_ok = now
302
303
gobject.source_remove(self.stop_initiator_tag)
303
304
self.stop_initiator_tag = gobject.timeout_add\
307
308
logger.warning(u"Checker for %(name)s crashed?",
310
logger.info(u"Checker for %(name)s failed",
311
logger.debug(u"Checker for %(name)s failed",
312
313
def start_checker(self):
313
314
"""Start a new checker subprocess if one is not running.
314
315
If a checker already exists, leave it running and do
337
338
u' %s', self.check_command, error)
338
339
return True # Try again later
340
logger.info(u"Starting checker %r for %s",
341
logger.debug(u"Starting checker %r for %s",
342
343
self.checker = subprocess.Popen(command,
344
345
shell=True, cwd="/")
396
397
def fingerprint(openpgp):
397
398
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
399
# New empty GnuTLS certificate
400
crt = gnutls.library.types.gnutls_openpgp_crt_t()
401
gnutls.library.functions.gnutls_openpgp_crt_init\
398
403
# New GnuTLS "datum" with the OpenPGP public key
399
404
datum = gnutls.library.types.gnutls_datum_t\
400
405
(ctypes.cast(ctypes.c_char_p(openpgp),
401
406
ctypes.POINTER(ctypes.c_ubyte)),
402
407
ctypes.c_uint(len(openpgp)))
403
# New empty GnuTLS certificate
404
crt = gnutls.library.types.gnutls_openpgp_crt_t()
405
gnutls.library.functions.gnutls_openpgp_crt_init\
407
408
# Import the OpenPGP public key into the certificate
408
gnutls.library.functions.gnutls_openpgp_crt_import\
409
(crt, ctypes.byref(datum),
410
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
409
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
412
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
411
413
# New buffer for the fingerprint
412
414
buffer = ctypes.create_string_buffer(20)
413
415
buffer_length = ctypes.c_size_t()
429
431
Note: This will run in its own forked process."""
431
433
def handle(self):
432
logger.info(u"TCP connection from: %s",
434
logger.debug(u"TCP connection from: %s",
433
435
unicode(self.client_address))
434
436
session = gnutls.connection.ClientSession\
435
437
(self.request, gnutls.connection.X509Credentials())
437
line = self.request.makefile().readline()
438
logger.debug(u"Protocol version: %r", line)
440
if int(line.strip().split()[0]) > 1:
442
except (ValueError, IndexError, RuntimeError), error:
443
logger.error(u"Unknown protocol version: %s", error)
446
438
# Note: gnutls.connection.X509Credentials is really a generic
447
439
# GnuTLS certificate credentials object so long as no X.509
448
440
# keys are added to it. Therefore, we can use it here despite
462
454
session.handshake()
463
455
except gnutls.errors.GNUTLSError, error:
464
logger.warning(u"Handshake failed: %s", error)
456
logger.debug(u"Handshake failed: %s", error)
465
457
# Do not run session.bye() here: the session is not
466
458
# established. Just abandon the request.
469
461
fpr = fingerprint(peer_certificate(session))
470
462
except (TypeError, gnutls.errors.GNUTLSError), error:
471
logger.warning(u"Bad certificate: %s", error)
463
logger.debug(u"Bad certificate: %s", error)
474
466
logger.debug(u"Fingerprint: %s", fpr)
481
logger.warning(u"Client not found for fingerprint: %s",
473
logger.debug(u"Client not found for fingerprint: %s", fpr)
485
476
# Have to check if client.still_valid(), since it is possible
486
477
# that the client timed out while establishing the GnuTLS
488
479
if not client.still_valid():
489
logger.warning(u"Client %(name)s is invalid",
480
logger.debug(u"Client %(name)s is invalid", vars(client))
519
509
"""This overrides the normal server_bind() function
520
510
to bind to an interface if one was specified, and also NOT to
521
511
bind to an address or port if they were not specified."""
522
if self.settings["interface"]:
512
if self.settings["interface"] != avahi.IF_UNSPEC:
523
513
# 25 is from /usr/include/asm-i486/socket.h
524
514
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
528
518
self.settings["interface"])
529
519
except socket.error, error:
530
520
if error[0] == errno.EPERM:
531
logger.error(u"No permission to"
532
u" bind to interface %s",
533
self.settings["interface"])
521
logger.warning(u"No permission to"
522
u" bind to interface %s",
523
self.settings["interface"])
536
526
# Only bind(2) the socket if we really need to.
582
572
def server_state_changed(state):
583
573
"""Derived from the Avahi example code"""
584
574
if state == avahi.SERVER_COLLISION:
585
logger.error(u"Server name collision")
575
logger.warning(u"Server name collision")
587
577
elif state == avahi.SERVER_RUNNING:
603
593
raise AvahiGroupError("State changed: %s", str(error))
605
def if_nametoindex(interface):
595
def if_nametoindex(interface, _func=[None]):
606
596
"""Call the C function if_nametoindex(), or equivalent"""
607
global if_nametoindex
597
if _func[0] is not None:
598
return _func[0](interface)
609
600
if "ctypes.util" not in sys.modules:
610
601
import ctypes.util
611
if_nametoindex = ctypes.cdll.LoadLibrary\
612
(ctypes.util.find_library("c")).if_nametoindex
604
libc = ctypes.cdll.LoadLibrary\
605
(ctypes.util.find_library("c"))
606
func[0] = libc.if_nametoindex
607
return _func[0](interface)
613
611
except (OSError, AttributeError):
614
612
if "struct" not in sys.modules:
616
614
if "fcntl" not in sys.modules:
618
def if_nametoindex(interface):
616
def the_hard_way(interface):
619
617
"Get an interface index the hard way, i.e. using fcntl()"
620
618
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
621
619
s = socket.socket()
752
751
def remove_from_clients(client):
753
752
clients.remove(client)
755
logger.critical(u"No clients left, exiting")
754
logger.debug(u"No clients left, exiting")
758
clients.update(Set(Client(name = section,
757
clients.update(Set(Client(name=section,
759
758
stop_hook = remove_from_clients,
761
= dict(client_config.items(section)))
759
**(dict(client_config\
762
761
for section in client_config.sections()))
796
795
# Find out what port we got
797
796
service.port = tcp_server.socket.getsockname()[1]
798
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
799
u" scope_id %d" % tcp_server.socket.getsockname())
797
logger.debug(u"Now listening on port %d", service.port)
801
#service.interface = tcp_server.socket.getsockname()[3]
799
if not server_settings["interface"]:
800
service.interface = if_nametoindex\
801
(server_settings["interface"])
804
804
# From the Avahi example code