9
41
import gnutls.crypto
10
42
import gnutls.connection
11
43
import gnutls.errors
44
import gnutls.library.functions
45
import gnutls.library.constants
46
import gnutls.library.types
12
47
import ConfigParser
57
import logging.handlers
62
from dbus.mainloop.glib import DBusGMainLoop
65
# Brief description of the operation of this program:
67
# This server announces itself as a Zeroconf service. Connecting
68
# clients use the TLS protocol, with the unusual quirk that this
69
# server program acts as a TLS "client" while the connecting clients
70
# acts as a TLS "server". The clients (acting as a TLS "server") must
71
# supply an OpenPGP certificate, and the fingerprint of this
72
# certificate is used by this server to look up (in a list read from a
73
# file at start time) which binary blob to give the client. No other
74
# authentication or authorization is done by this server.
77
logger = logging.Logger('mandos')
78
syslogger = logging.handlers.SysLogHandler\
79
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
80
syslogger.setFormatter(logging.Formatter\
81
('%(levelname)s: %(message)s'))
82
logger.addHandler(syslogger)
85
# This variable is used to optionally bind to a specified interface.
86
# It is a global variable to fit in with the other variables from the
88
serviceInterface = avahi.IF_UNSPEC
89
# From the Avahi example code:
90
serviceName = "Mandos"
91
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
servicePort = None # Not known at startup
93
serviceTXT = [] # TXT record for the service
94
domain = "" # Domain to publish on, default to .local
95
host = "" # Host to publish records for, default to localhost
96
group = None #our entry group
97
rename_count = 12 # Counter so we only rename after collisions a
98
# sensible number of times
99
# End of Avahi example code
14
102
class Client(object):
15
def __init__(self, name=None, dn=None, password=None,
16
passfile=None, fqdn=None, timeout=None,
103
"""A representation of a client host served by this server.
105
name: string; from the config file, used in log messages
106
fingerprint: string (40 or 32 hexadecimal digits); used to
107
uniquely identify the client
108
secret: bytestring; sent verbatim (over TLS) to client
109
fqdn: string (FQDN); available for use by the checker command
110
created: datetime.datetime()
111
last_seen: datetime.datetime() or None if not yet seen
112
timeout: datetime.timedelta(); How long from last_seen until
113
this client is invalid
114
interval: datetime.timedelta(); How often to start a new checker
115
stop_hook: If set, called by stop() as stop_hook(self)
116
checker: subprocess.Popen(); a running checker process used
117
to see if the client lives.
118
Is None if no process is running.
119
checker_initiator_tag: a gobject event source tag, or None
120
stop_initiator_tag: - '' -
121
checker_callback_tag: - '' -
122
checker_command: string; External command which is run to check if
123
client lives. %()s expansions are done at
124
runtime with vars(self) as dict, so that for
125
instance %(name)s can be used in the command.
127
_timeout: Real variable for 'timeout'
128
_interval: Real variable for 'interval'
129
_timeout_milliseconds: Used by gobject.timeout_add()
130
_interval_milliseconds: - '' -
132
def _set_timeout(self, timeout):
133
"Setter function for 'timeout' attribute"
134
self._timeout = timeout
135
self._timeout_milliseconds = ((self.timeout.days
136
* 24 * 60 * 60 * 1000)
137
+ (self.timeout.seconds * 1000)
138
+ (self.timeout.microseconds
140
timeout = property(lambda self: self._timeout,
143
def _set_interval(self, interval):
144
"Setter function for 'interval' attribute"
145
self._interval = interval
146
self._interval_milliseconds = ((self.interval.days
147
* 24 * 60 * 60 * 1000)
148
+ (self.interval.seconds
150
+ (self.interval.microseconds
152
interval = property(lambda self: self._interval,
155
def __init__(self, name=None, options=None, stop_hook=None,
156
fingerprint=None, secret=None, secfile=None,
157
fqdn=None, timeout=None, interval=-1, checker=None):
158
"""Note: the 'checker' argument sets the 'checker_command'
159
attribute and not the 'checker' attribute.."""
21
self.password = password
23
self.password = open(passfile).readall()
161
# Uppercase and remove spaces from fingerprint
162
# for later comparison purposes with return value of
163
# the fingerprint() function
164
self.fingerprint = fingerprint.upper().replace(u" ", u"")
166
self.secret = secret.decode(u"base64")
169
self.secret = sf.read()
25
print "No Password or Passfile in client config file"
26
# raise RuntimeError XXX
27
self.password = "gazonk"
172
raise RuntimeError(u"No secret or secfile for client %s"
174
self.fqdn = fqdn # string
175
self.created = datetime.datetime.now()
30
176
self.last_seen = None
31
177
if timeout is None:
32
timeout = self.server.options.timeout
33
self.timeout = timeout
178
self.timeout = options.timeout
180
self.timeout = string_to_delta(timeout)
34
181
if interval == -1:
35
interval = self.server.options.interval
36
self.interval = interval
38
def server_bind(self):
39
if self.options.interface:
40
if not hasattr(socket, "SO_BINDTODEVICE"):
41
# From /usr/include/asm-i486/socket.h
42
socket.SO_BINDTODEVICE = 25
44
self.socket.setsockopt(socket.SOL_SOCKET,
45
socket.SO_BINDTODEVICE,
46
self.options.interface)
47
except socket.error, error:
48
if error[0] == errno.EPERM:
49
print "Warning: Denied permission to bind to interface", \
50
self.options.interface
182
self.interval = options.interval
184
self.interval = string_to_delta(interval)
185
self.stop_hook = stop_hook
187
self.checker_initiator_tag = None
188
self.stop_initiator_tag = None
189
self.checker_callback_tag = None
190
self.check_command = checker
192
"""Start this client's checker and timeout hooks"""
193
# Schedule a new checker to be started an 'interval' from now,
194
# and every interval from then on.
195
self.checker_initiator_tag = gobject.timeout_add\
196
(self._interval_milliseconds,
198
# Also start a new checker *right now*.
200
# Schedule a stop() when 'timeout' has passed
201
self.stop_initiator_tag = gobject.timeout_add\
202
(self._timeout_milliseconds,
206
The possibility that this client might be restarted is left
207
open, but not currently used."""
208
# If this client doesn't have a secret, it is already stopped.
210
logger.debug(u"Stopping client %s", self.name)
214
if hasattr(self, "stop_initiator_tag") \
215
and self.stop_initiator_tag:
216
gobject.source_remove(self.stop_initiator_tag)
217
self.stop_initiator_tag = None
218
if hasattr(self, "checker_initiator_tag") \
219
and self.checker_initiator_tag:
220
gobject.source_remove(self.checker_initiator_tag)
221
self.checker_initiator_tag = None
225
# Do not run this again if called by a gobject.timeout_add
228
self.stop_hook = None
230
def checker_callback(self, pid, condition):
231
"""The checker has completed, so take appropriate actions."""
232
now = datetime.datetime.now()
233
self.checker_callback_tag = None
235
if os.WIFEXITED(condition) \
236
and (os.WEXITSTATUS(condition) == 0):
237
logger.debug(u"Checker for %(name)s succeeded",
240
gobject.source_remove(self.stop_initiator_tag)
241
self.stop_initiator_tag = gobject.timeout_add\
242
(self._timeout_milliseconds,
244
elif not os.WIFEXITED(condition):
245
logger.warning(u"Checker for %(name)s crashed?",
248
logger.debug(u"Checker for %(name)s failed",
250
def start_checker(self):
251
"""Start a new checker subprocess if one is not running.
252
If a checker already exists, leave it running and do
254
# The reason for not killing a running checker is that if we
255
# did that, then if a checker (for some reason) started
256
# running slowly and taking more than 'interval' time, the
257
# client would inevitably timeout, since no checker would get
258
# a chance to run to completion. If we instead leave running
259
# checkers alone, the checker would have to take more time
260
# than 'timeout' for the client to be declared invalid, which
261
# is as it should be.
262
if self.checker is None:
264
command = self.check_command % self.fqdn
266
escaped_attrs = dict((key, re.escape(str(val)))
268
vars(self).iteritems())
270
command = self.check_command % escaped_attrs
271
except TypeError, error:
272
logger.critical(u'Could not format string "%s":'
273
u' %s', self.check_command, error)
274
return True # Try again later
276
logger.debug(u"Starting checker %r for %s",
278
self.checker = subprocess.\
280
close_fds=True, shell=True,
282
self.checker_callback_tag = gobject.child_watch_add\
284
self.checker_callback)
285
except subprocess.OSError, error:
286
logger.error(u"Failed to start subprocess: %s",
288
# Re-run this periodically if run by gobject.timeout_add
290
def stop_checker(self):
291
"""Force the checker process, if any, to stop."""
292
if self.checker_callback_tag:
293
gobject.source_remove(self.checker_callback_tag)
294
self.checker_callback_tag = None
295
if not hasattr(self, "checker") or self.checker is None:
297
logger.debug("Stopping checker for %(name)s", vars(self))
299
os.kill(self.checker.pid, signal.SIGTERM)
301
#if self.checker.poll() is None:
302
# os.kill(self.checker.pid, signal.SIGKILL)
303
except OSError, error:
304
if error.errno != errno.ESRCH:
307
def still_valid(self, now=None):
308
"""Has the timeout not yet passed for this client?"""
310
now = datetime.datetime.now()
311
if self.last_seen is None:
312
return now < (self.created + self.timeout)
314
return now < (self.last_seen + self.timeout)
317
def peer_certificate(session):
318
"Return the peer's OpenPGP certificate as a bytestring"
319
# If not an OpenPGP certificate...
320
if gnutls.library.functions.gnutls_certificate_type_get\
321
(session._c_object) \
322
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
323
# ...do the normal thing
324
return session.peer_certificate
325
list_size = ctypes.c_uint()
326
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
327
(session._c_object, ctypes.byref(list_size))
328
if list_size.value == 0:
331
return ctypes.string_at(cert.data, cert.size)
334
def fingerprint(openpgp):
335
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
336
# New empty GnuTLS certificate
337
crt = gnutls.library.types.gnutls_openpgp_crt_t()
338
gnutls.library.functions.gnutls_openpgp_crt_init\
340
# New GnuTLS "datum" with the OpenPGP public key
341
datum = gnutls.library.types.gnutls_datum_t\
342
(ctypes.cast(ctypes.c_char_p(openpgp),
343
ctypes.POINTER(ctypes.c_ubyte)),
344
ctypes.c_uint(len(openpgp)))
345
# Import the OpenPGP public key into the certificate
346
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
349
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
350
# New buffer for the fingerprint
351
buffer = ctypes.create_string_buffer(20)
352
buffer_length = ctypes.c_size_t()
353
# Get the fingerprint from the certificate into the buffer
354
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
355
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
356
# Deinit the certificate
357
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
358
# Convert the buffer to a Python bytestring
359
fpr = ctypes.string_at(buffer, buffer_length.value)
360
# Convert the bytestring to hexadecimal notation
361
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
365
class tcp_handler(SocketServer.BaseRequestHandler, object):
366
"""A TCP request handler class.
367
Instantiated by IPv6_TCPServer for each request to handle it.
368
Note: This will run in its own forked process."""
371
logger.debug(u"TCP connection from: %s",
372
unicode(self.client_address))
373
session = gnutls.connection.ClientSession(self.request,
377
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
378
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
380
priority = "SECURE256"
382
gnutls.library.functions.gnutls_priority_set_direct\
383
(session._c_object, priority, None);
387
except gnutls.errors.GNUTLSError, error:
388
logger.debug(u"Handshake failed: %s", error)
389
# Do not run session.bye() here: the session is not
390
# established. Just abandon the request.
393
fpr = fingerprint(peer_certificate(session))
394
except (TypeError, gnutls.errors.GNUTLSError), error:
395
logger.debug(u"Bad certificate: %s", error)
398
logger.debug(u"Fingerprint: %s", fpr)
400
for c in self.server.clients:
401
if c.fingerprint == fpr:
404
# Have to check if client.still_valid(), since it is possible
405
# that the client timed out while establishing the GnuTLS
407
if (not client) or (not client.still_valid()):
409
logger.debug(u"Client %(name)s is invalid",
53
return super(type(self), self).server_bind()
56
def init_with_options(self, *args, **kwargs):
57
if "options" in kwargs:
58
self.options = kwargs["options"]
60
if "clients" in kwargs:
61
self.clients = kwargs["clients"]
63
if "credentials" in kwargs:
64
self.credentials = kwargs["credentials"]
65
del kwargs["credentials"]
66
return super(type(self), self).__init__(*args, **kwargs)
69
class udp_handler(SocketServer.DatagramRequestHandler, object):
71
self.wfile.write("Polo")
72
print "UDP request answered"
75
class IPv6_UDPServer(SocketServer.UDPServer, object):
76
__init__ = init_with_options
77
address_family = socket.AF_INET6
78
allow_reuse_address = True
79
server_bind = server_bind
80
def verify_request(self, request, client_address):
81
print "UDP request came"
82
return request[0] == "Marco"
85
class tcp_handler(SocketServer.BaseRequestHandler, object):
87
print "TCP request came"
88
print "Request:", self.request
89
print "Client Address:", self.client_address
90
print "Server:", self.server
91
session = gnutls.connection.ServerSession(self.request,
92
self.server.credentials)
94
if session.peer_certificate:
95
print "DN:", session.peer_certificate.subject
98
except gnutls.errors.CertificateError, error:
99
print "Verify failed", error
412
logger.debug(u"Client not found for fingerprint: %s",
103
session.send(dict((client.dn, client.password)
104
for client in self.server.clients)
105
[session.peer_certificate.subject])
107
session.send("gazonk")
417
while sent_size < len(client.secret):
418
sent = session.send(client.secret[sent_size:])
419
logger.debug(u"Sent: %d, remaining: %d",
420
sent, len(client.secret)
421
- (sent_size + sent))
111
426
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
112
__init__ = init_with_options
427
"""IPv6 TCP server. Accepts 'None' as address and/or port.
429
options: Command line options
430
clients: Set() of Client objects
113
432
address_family = socket.AF_INET6
114
allow_reuse_address = True
115
request_queue_size = 1024
116
server_bind = server_bind
433
def __init__(self, *args, **kwargs):
434
if "options" in kwargs:
435
self.options = kwargs["options"]
436
del kwargs["options"]
437
if "clients" in kwargs:
438
self.clients = kwargs["clients"]
439
del kwargs["clients"]
440
return super(type(self), self).__init__(*args, **kwargs)
441
def server_bind(self):
442
"""This overrides the normal server_bind() function
443
to bind to an interface if one was specified, and also NOT to
444
bind to an address or port if they were not specified."""
445
if self.options.interface:
446
if not hasattr(socket, "SO_BINDTODEVICE"):
447
# From /usr/include/asm-i486/socket.h
448
socket.SO_BINDTODEVICE = 25
450
self.socket.setsockopt(socket.SOL_SOCKET,
451
socket.SO_BINDTODEVICE,
452
self.options.interface)
453
except socket.error, error:
454
if error[0] == errno.EPERM:
455
logger.warning(u"No permission to"
456
u" bind to interface %s",
457
self.options.interface)
460
# Only bind(2) the socket if we really need to.
461
if self.server_address[0] or self.server_address[1]:
462
if not self.server_address[0]:
464
self.server_address = (in6addr_any,
465
self.server_address[1])
466
elif self.server_address[1] is None:
467
self.server_address = (self.server_address[0],
469
return super(type(self), self).server_bind()
472
def string_to_delta(interval):
473
"""Parse a string and return a datetime.timedelta
475
>>> string_to_delta('7d')
476
datetime.timedelta(7)
477
>>> string_to_delta('60s')
478
datetime.timedelta(0, 60)
479
>>> string_to_delta('60m')
480
datetime.timedelta(0, 3600)
481
>>> string_to_delta('24h')
482
datetime.timedelta(1)
483
>>> string_to_delta(u'1w')
484
datetime.timedelta(7)
487
suffix=unicode(interval[-1])
488
value=int(interval[:-1])
490
delta = datetime.timedelta(value)
492
delta = datetime.timedelta(0, value)
494
delta = datetime.timedelta(0, 0, 0, 0, value)
496
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
498
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
501
except (ValueError, IndexError):
507
"""Derived from the Avahi example code"""
508
global group, serviceName, serviceType, servicePort, serviceTXT, \
511
group = dbus.Interface(
512
bus.get_object( avahi.DBUS_NAME,
513
server.EntryGroupNew()),
514
avahi.DBUS_INTERFACE_ENTRY_GROUP)
515
group.connect_to_signal('StateChanged',
516
entry_group_state_changed)
517
logger.debug(u"Adding service '%s' of type '%s' ...",
518
serviceName, serviceType)
521
serviceInterface, # interface
522
avahi.PROTO_INET6, # protocol
523
dbus.UInt32(0), # flags
524
serviceName, serviceType,
526
dbus.UInt16(servicePort),
527
avahi.string_array_to_txt_array(serviceTXT))
531
def remove_service():
532
"""From the Avahi example code"""
535
if not group is None:
539
def server_state_changed(state):
540
"""Derived from the Avahi example code"""
541
if state == avahi.SERVER_COLLISION:
542
logger.warning(u"Server name collision")
544
elif state == avahi.SERVER_RUNNING:
548
def entry_group_state_changed(state, error):
549
"""Derived from the Avahi example code"""
550
global serviceName, server, rename_count
552
logger.debug(u"state change: %i", state)
554
if state == avahi.ENTRY_GROUP_ESTABLISHED:
555
logger.debug(u"Service established.")
556
elif state == avahi.ENTRY_GROUP_COLLISION:
558
rename_count = rename_count - 1
560
name = server.GetAlternativeServiceName(name)
561
logger.warning(u"Service name collision, "
562
u"changing name to '%s' ...", name)
567
logger.error(u"No suitable service name found after %i"
568
u" retries, exiting.", n_rename)
570
elif state == avahi.ENTRY_GROUP_FAILURE:
571
logger.error(u"Error in group state changed %s",
576
def if_nametoindex(interface):
577
"""Call the C function if_nametoindex()"""
579
libc = ctypes.cdll.LoadLibrary("libc.so.6")
580
return libc.if_nametoindex(interface)
581
except (OSError, AttributeError):
582
if "struct" not in sys.modules:
584
if "fcntl" not in sys.modules:
586
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
588
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
589
struct.pack("16s16x", interface))
591
interface_index = struct.unpack("I", ifreq[16:20])[0]
592
return interface_index
595
def daemon(nochdir, noclose):
596
"""See daemon(3). Standard BSD Unix function.
597
This should really exist as os.daemon, but it doesn't (yet)."""
604
# Close all standard open file descriptors
605
null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
606
if not stat.S_ISCHR(os.fstat(null).st_mode):
607
raise OSError(errno.ENODEV,
608
"/dev/null not a character device")
609
os.dup2(null, sys.stdin.fileno())
610
os.dup2(null, sys.stdout.fileno())
611
os.dup2(null, sys.stderr.fileno())
616
def killme(status = 0):
617
logger.debug("Stopping server with exit status %d", status)
619
if main_loop_started:
628
global main_loop_started
629
main_loop_started = False
124
631
parser = OptionParser()
125
632
parser.add_option("-i", "--interface", type="string",
126
default="eth0", metavar="IF",
127
help="Interface to bind to")
128
parser.add_option("--cert", type="string", default="cert.pem",
130
help="Public key certificate to use")
131
parser.add_option("--key", type="string", default="key.pem",
133
help="Private key to use")
134
parser.add_option("--ca", type="string", default="ca.pem",
136
help="Certificate Authority certificate to use")
137
parser.add_option("--crl", type="string", default="crl.pem",
139
help="Certificate Revokation List to use")
140
parser.add_option("-p", "--port", type="int", default=49001,
633
default=None, metavar="IF",
634
help="Bind to interface IF")
635
parser.add_option("-a", "--address", type="string", default=None,
636
help="Address to listen for requests on")
637
parser.add_option("-p", "--port", type="int", default=None,
141
638
help="Port number to receive requests on")
142
parser.add_option("--dh", type="int", metavar="BITS",
143
help="DH group to use")
144
parser.add_option("-t", "--timeout", type="string", # Parsed later
639
parser.add_option("--timeout", type="string", # Parsed later
146
641
help="Amount of downtime allowed for clients")
642
parser.add_option("--interval", type="string", # Parsed later
644
help="How often to check that a client is up")
645
parser.add_option("--check", action="store_true", default=False,
646
help="Run self-test")
647
parser.add_option("--debug", action="store_true", default=False,
147
649
(options, args) = parser.parse_args()
149
# Parse the time argument
656
# Parse the time arguments
151
suffix=options.timeout[-1]
152
value=int(options.timeout[:-1])
154
options.timeout = datetime.timedelta(value)
156
options.timeout = datetime.timedelta(0, value)
158
options.timeout = datetime.timedelta(0, 0, 0, 0, value)
160
options.timeout = datetime.timedelta(0, 0, 0, 0, 0, value)
162
options.timeout = datetime.timedelta(0, 0, 0, 0, 0, 0,
166
except (ValueError, IndexError):
658
options.timeout = string_to_delta(options.timeout)
167
660
parser.error("option --timeout: Unparseable time")
169
cert = gnutls.crypto.X509Certificate(open(options.cert).read())
170
key = gnutls.crypto.X509PrivateKey(open(options.key).read())
171
ca = gnutls.crypto.X509Certificate(open(options.ca).read())
172
crl = gnutls.crypto.X509CRL(open(options.crl).read())
173
cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
662
options.interval = string_to_delta(options.interval)
664
parser.error("option --interval: Unparseable time")
175
666
# Parse config file
177
client_config_object = ConfigParser.SafeConfigParser(defaults)
178
client_config_object.read("mandos-clients.conf")
179
clients = [Client(name=section,
180
**(dict(client_config_object.items(section))))
181
for section in client_config_object.sections()]
183
udp_server = IPv6_UDPServer((in6addr_any, options.port),
187
tcp_server = IPv6_TCPServer((in6addr_any, options.port),
667
defaults = { "checker": "fping -q -- %%(fqdn)s" }
668
client_config = ConfigParser.SafeConfigParser(defaults)
669
#client_config.readfp(open("global.conf"), "global.conf")
670
client_config.read("mandos-clients.conf")
675
# From the Avahi example code
676
DBusGMainLoop(set_as_default=True )
677
main_loop = gobject.MainLoop()
678
bus = dbus.SystemBus()
679
server = dbus.Interface(
680
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
681
avahi.DBUS_INTERFACE_SERVER )
682
# End of Avahi example code
684
debug = options.debug
687
console = logging.StreamHandler()
688
# console.setLevel(logging.DEBUG)
689
console.setFormatter(logging.Formatter\
690
('%(levelname)s: %(message)s'))
691
logger.addHandler(console)
695
def remove_from_clients(client):
696
clients.remove(client)
698
logger.debug(u"No clients left, exiting")
701
clients.update(Set(Client(name=section, options=options,
702
stop_hook = remove_from_clients,
703
**(dict(client_config\
705
for section in client_config.sections()))
711
"Cleanup function; run on exit"
713
# From the Avahi example code
714
if not group is None:
717
# End of Avahi example code
720
client = clients.pop()
721
client.stop_hook = None
724
atexit.register(cleanup)
727
signal.signal(signal.SIGINT, signal.SIG_IGN)
728
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
729
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
731
for client in clients:
734
tcp_server = IPv6_TCPServer((options.address, options.port),
194
in_, out, err = select.select((udp_server,
197
server.handle_request()
200
if __name__ == "__main__":
738
# Find out what random port we got
740
servicePort = tcp_server.socket.getsockname()[1]
741
logger.debug(u"Now listening on port %d", servicePort)
743
if options.interface is not None:
744
global serviceInterface
745
serviceInterface = if_nametoindex(options.interface)
747
# From the Avahi example code
748
server.connect_to_signal("StateChanged", server_state_changed)
750
server_state_changed(server.GetState())
751
except dbus.exceptions.DBusException, error:
752
logger.critical(u"DBusException: %s", error)
754
# End of Avahi example code
756
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
757
lambda *args, **kwargs:
758
tcp_server.handle_request(*args[2:],
761
logger.debug("Starting main loop")
762
main_loop_started = True
764
except KeyboardInterrupt:
770
if __name__ == '__main__':