9
40
import gnutls.crypto
10
41
import gnutls.connection
11
42
import gnutls.errors
43
import gnutls.library.functions
44
import gnutls.library.constants
45
import gnutls.library.types
12
46
import ConfigParser
56
import logging.handlers
61
from dbus.mainloop.glib import DBusGMainLoop
64
# Brief description of the operation of this program:
66
# This server announces itself as a Zeroconf service. Connecting
67
# clients use the TLS protocol, with the unusual quirk that this
68
# server program acts as a TLS "client" while a connecting client acts
69
# as a TLS "server". The client (acting as a TLS "server") must
70
# supply an OpenPGP certificate, and the fingerprint of this
71
# certificate is used by this server to look up (in a list read from a
72
# file at start time) which binary blob to give the client. No other
73
# authentication or authorization is done by this server.
76
logger = logging.Logger('mandos')
77
syslogger = logging.handlers.SysLogHandler\
78
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
79
syslogger.setFormatter(logging.Formatter\
80
('%(levelname)s: %(message)s'))
81
logger.addHandler(syslogger)
85
class AvahiError(Exception):
86
def __init__(self, value):
89
return repr(self.value)
91
class AvahiServiceError(AvahiError):
94
class AvahiGroupError(AvahiError):
98
class AvahiService(object):
100
interface: integer; avahi.IF_UNSPEC or an interface index.
101
Used to optionally bind to the specified interface.
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
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
115
type = None, port = None, TXT = None, domain = "",
116
host = "", max_renames = 12):
117
"""An Avahi (Zeroconf) service. """
118
self.interface = interface
128
self.rename_count = 0
130
"""Derived from the Avahi example code"""
131
if self.rename_count >= self.max_renames:
132
logger.critical(u"No suitable service name found after %i"
133
u" retries, exiting.", rename_count)
134
raise AvahiServiceError("Too many renames")
135
name = server.GetAlternativeServiceName(name)
136
logger.notice(u"Changing name to %r ...", name)
139
self.rename_count += 1
141
"""Derived from the Avahi example code"""
142
if group is not None:
145
"""Derived from the Avahi example code"""
148
group = dbus.Interface\
149
(bus.get_object(avahi.DBUS_NAME,
150
server.EntryGroupNew()),
151
avahi.DBUS_INTERFACE_ENTRY_GROUP)
152
group.connect_to_signal('StateChanged',
153
entry_group_state_changed)
154
logger.debug(u"Adding service '%s' of type '%s' ...",
155
service.name, service.type)
157
self.interface, # interface
158
avahi.PROTO_INET6, # protocol
159
dbus.UInt32(0), # flags
160
self.name, self.type,
161
self.domain, self.host,
162
dbus.UInt16(self.port),
163
avahi.string_array_to_txt_array(self.TXT))
166
# From the Avahi example code:
167
group = None # our entry group
168
# End of Avahi example code
16
171
class Client(object):
17
def __init__(self, name=None, options=None, dn=None,
18
password=None, passfile=None, fqdn=None,
19
timeout=None, interval=-1):
172
"""A representation of a client host served by this server.
174
name: string; from the config file, used in log messages
175
fingerprint: string (40 or 32 hexadecimal digits); used to
176
uniquely identify the client
177
secret: bytestring; sent verbatim (over TLS) to client
178
fqdn: string (FQDN); available for use by the checker command
179
created: datetime.datetime(); object creation, not client host
180
last_checked_ok: datetime.datetime() or None if not yet checked OK
181
timeout: datetime.timedelta(); How long from last_checked_ok
182
until this client is invalid
183
interval: datetime.timedelta(); How often to start a new checker
184
stop_hook: If set, called by stop() as stop_hook(self)
185
checker: subprocess.Popen(); a running checker process used
186
to see if the client lives.
187
'None' if no process is running.
188
checker_initiator_tag: a gobject event source tag, or None
189
stop_initiator_tag: - '' -
190
checker_callback_tag: - '' -
191
checker_command: string; External command which is run to check if
192
client lives. %() expansions are done at
193
runtime with vars(self) as dict, so that for
194
instance %(name)s can be used in the command.
196
_timeout: Real variable for 'timeout'
197
_interval: Real variable for 'interval'
198
_timeout_milliseconds: Used when calling gobject.timeout_add()
199
_interval_milliseconds: - '' -
201
def _set_timeout(self, timeout):
202
"Setter function for 'timeout' attribute"
203
self._timeout = timeout
204
self._timeout_milliseconds = ((self.timeout.days
205
* 24 * 60 * 60 * 1000)
206
+ (self.timeout.seconds * 1000)
207
+ (self.timeout.microseconds
209
timeout = property(lambda self: self._timeout,
212
def _set_interval(self, interval):
213
"Setter function for 'interval' attribute"
214
self._interval = interval
215
self._interval_milliseconds = ((self.interval.days
216
* 24 * 60 * 60 * 1000)
217
+ (self.interval.seconds
219
+ (self.interval.microseconds
221
interval = property(lambda self: self._interval,
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.."""
23
self.password = password
25
self.password = open(passfile).readall()
230
logger.debug(u"Creating client %r", self.name)
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"")
235
logger.debug(u" Fingerprint: %s", self.fingerprint)
237
self.secret = secret.decode(u"base64")
240
self.secret = sf.read()
27
print "No Password or Passfile in client config file"
28
# raise RuntimeError XXX
29
self.password = "gazonk"
243
raise TypeError(u"No secret or secfile for client %s"
31
246
self.created = datetime.datetime.now()
34
timeout = options.timeout
35
self.timeout = timeout
37
interval = options.interval
38
self.interval = interval
39
self.next_check = datetime.datetime.now()
42
class server_metaclass(type):
43
"Common behavior for the UDP and TCP server classes"
44
def __new__(cls, name, bases, attrs):
45
attrs["address_family"] = socket.AF_INET6
46
attrs["allow_reuse_address"] = True
47
def server_bind(self):
48
if self.options.interface:
49
if not hasattr(socket, "SO_BINDTODEVICE"):
50
# From /usr/include/asm-i486/socket.h
51
socket.SO_BINDTODEVICE = 25
247
self.last_checked_ok = None
248
self.timeout = string_to_delta(timeout)
249
self.interval = string_to_delta(interval)
250
self.stop_hook = stop_hook
252
self.checker_initiator_tag = None
253
self.stop_initiator_tag = None
254
self.checker_callback_tag = None
255
self.check_command = checker
257
"""Start this client's checker and timeout hooks"""
258
# Schedule a new checker to be started an 'interval' from now,
259
# and every interval from then on.
260
self.checker_initiator_tag = gobject.timeout_add\
261
(self._interval_milliseconds,
263
# Also start a new checker *right now*.
265
# Schedule a stop() when 'timeout' has passed
266
self.stop_initiator_tag = gobject.timeout_add\
267
(self._timeout_milliseconds,
271
The possibility that a client might be restarted is left open,
272
but not currently used."""
273
# If this client doesn't have a secret, it is already stopped.
275
logger.debug(u"Stopping client %s", self.name)
279
if getattr(self, "stop_initiator_tag", False):
280
gobject.source_remove(self.stop_initiator_tag)
281
self.stop_initiator_tag = None
282
if getattr(self, "checker_initiator_tag", False):
283
gobject.source_remove(self.checker_initiator_tag)
284
self.checker_initiator_tag = None
288
# Do not run this again if called by a gobject.timeout_add
291
self.stop_hook = None
293
def checker_callback(self, pid, condition):
294
"""The checker has completed, so take appropriate actions."""
295
now = datetime.datetime.now()
296
self.checker_callback_tag = None
298
if os.WIFEXITED(condition) \
299
and (os.WEXITSTATUS(condition) == 0):
300
logger.debug(u"Checker for %(name)s succeeded",
302
self.last_checked_ok = now
303
gobject.source_remove(self.stop_initiator_tag)
304
self.stop_initiator_tag = gobject.timeout_add\
305
(self._timeout_milliseconds,
307
elif not os.WIFEXITED(condition):
308
logger.warning(u"Checker for %(name)s crashed?",
311
logger.debug(u"Checker for %(name)s failed",
313
def start_checker(self):
314
"""Start a new checker subprocess if one is not running.
315
If a checker already exists, leave it running and do
317
# The reason for not killing a running checker is that if we
318
# did that, then if a checker (for some reason) started
319
# running slowly and taking more than 'interval' time, the
320
# client would inevitably timeout, since no checker would get
321
# a chance to run to completion. If we instead leave running
322
# checkers alone, the checker would have to take more time
323
# than 'timeout' for the client to be declared invalid, which
324
# is as it should be.
325
if self.checker is None:
327
# In case check_command has exactly one % operator
328
command = self.check_command % self.fqdn
330
# Escape attributes for the shell
331
escaped_attrs = dict((key, re.escape(str(val)))
333
vars(self).iteritems())
53
self.socket.setsockopt(socket.SOL_SOCKET,
54
socket.SO_BINDTODEVICE,
55
self.options.interface)
56
except socket.error, error:
57
if error[0] == errno.EPERM:
58
print "Warning: No permission to bind to interface", \
59
self.options.interface
62
return super(type(self), self).server_bind()
63
attrs["server_bind"] = server_bind
64
def init(self, *args, **kwargs):
65
if "options" in kwargs:
66
self.options = kwargs["options"]
68
if "clients" in kwargs:
69
self.clients = kwargs["clients"]
71
if "credentials" in kwargs:
72
self.credentials = kwargs["credentials"]
73
del kwargs["credentials"]
74
return super(type(self), self).__init__(*args, **kwargs)
75
attrs["__init__"] = init
76
return type.__new__(cls, name, bases, attrs)
79
class udp_handler(SocketServer.DatagramRequestHandler, object):
81
self.wfile.write("Polo")
82
print "UDP request answered"
85
class IPv6_UDPServer(SocketServer.UDPServer, object):
86
__metaclass__ = server_metaclass
87
def verify_request(self, request, client_address):
88
print "UDP request came"
89
return request[0] == "Marco"
335
command = self.check_command % escaped_attrs
336
except TypeError, error:
337
logger.error(u'Could not format string "%s":'
338
u' %s', self.check_command, error)
339
return True # Try again later
341
logger.debug(u"Starting checker %r for %s",
343
self.checker = subprocess.Popen(command,
346
self.checker_callback_tag = gobject.child_watch_add\
348
self.checker_callback)
349
except subprocess.OSError, error:
350
logger.error(u"Failed to start subprocess: %s",
352
# Re-run this periodically if run by gobject.timeout_add
354
def stop_checker(self):
355
"""Force the checker process, if any, to stop."""
356
if self.checker_callback_tag:
357
gobject.source_remove(self.checker_callback_tag)
358
self.checker_callback_tag = None
359
if getattr(self, "checker", None) is None:
361
logger.debug("Stopping checker for %(name)s", vars(self))
363
os.kill(self.checker.pid, signal.SIGTERM)
365
#if self.checker.poll() is None:
366
# os.kill(self.checker.pid, signal.SIGKILL)
367
except OSError, error:
368
if error.errno != errno.ESRCH: # No such process
371
def still_valid(self):
372
"""Has the timeout not yet passed for this client?"""
373
now = datetime.datetime.now()
374
if self.last_checked_ok is None:
375
return now < (self.created + self.timeout)
377
return now < (self.last_checked_ok + self.timeout)
380
def peer_certificate(session):
381
"Return the peer's OpenPGP certificate as a bytestring"
382
# If not an OpenPGP certificate...
383
if gnutls.library.functions.gnutls_certificate_type_get\
384
(session._c_object) \
385
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
386
# ...do the normal thing
387
return session.peer_certificate
388
list_size = ctypes.c_uint()
389
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
390
(session._c_object, ctypes.byref(list_size))
391
if list_size.value == 0:
394
return ctypes.string_at(cert.data, cert.size)
397
def fingerprint(openpgp):
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\
403
# New GnuTLS "datum" with the OpenPGP public key
404
datum = gnutls.library.types.gnutls_datum_t\
405
(ctypes.cast(ctypes.c_char_p(openpgp),
406
ctypes.POINTER(ctypes.c_ubyte)),
407
ctypes.c_uint(len(openpgp)))
408
# Import the OpenPGP public key into the certificate
409
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
412
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
413
# New buffer for the fingerprint
414
buffer = ctypes.create_string_buffer(20)
415
buffer_length = ctypes.c_size_t()
416
# Get the fingerprint from the certificate into the buffer
417
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
418
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
419
# Deinit the certificate
420
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
421
# Convert the buffer to a Python bytestring
422
fpr = ctypes.string_at(buffer, buffer_length.value)
423
# Convert the bytestring to hexadecimal notation
424
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
92
428
class tcp_handler(SocketServer.BaseRequestHandler, object):
429
"""A TCP request handler class.
430
Instantiated by IPv6_TCPServer for each request to handle it.
431
Note: This will run in its own forked process."""
94
print "TCP request came"
95
print "Request:", self.request
96
print "Client Address:", self.client_address
97
print "Server:", self.server
98
session = gnutls.connection.ServerSession(self.request,
99
self.server.credentials)
101
if session.peer_certificate:
102
print "DN:", session.peer_certificate.subject
104
session.verify_peer()
105
except gnutls.errors.CertificateError, error:
106
print "Verify failed", error
110
session.send(dict((client.dn, client.password)
111
for client in self.server.clients)
112
[session.peer_certificate.subject])
114
session.send("gazonk")
434
logger.debug(u"TCP connection from: %s",
435
unicode(self.client_address))
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
session = gnutls.connection.ClientSession\
447
(self.request, gnutls.connection.X509Credentials())
448
# Note: gnutls.connection.X509Credentials is really a generic
449
# GnuTLS certificate credentials object so long as no X.509
450
# keys are added to it. Therefore, we can use it here despite
451
# using OpenPGP certificates.
453
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
454
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
456
priority = "NORMAL" # Fallback default, since this
458
if self.server.settings["priority"]:
459
priority = self.server.settings["priority"]
460
gnutls.library.functions.gnutls_priority_set_direct\
461
(session._c_object, priority, None);
465
except gnutls.errors.GNUTLSError, error:
466
logger.debug(u"Handshake failed: %s", error)
467
# Do not run session.bye() here: the session is not
468
# established. Just abandon the request.
471
fpr = fingerprint(peer_certificate(session))
472
except (TypeError, gnutls.errors.GNUTLSError), error:
473
logger.debug(u"Bad certificate: %s", error)
476
logger.debug(u"Fingerprint: %s", fpr)
478
for c in self.server.clients:
479
if c.fingerprint == fpr:
483
logger.debug(u"Client not found for fingerprint: %s", fpr)
486
# Have to check if client.still_valid(), since it is possible
487
# that the client timed out while establishing the GnuTLS
489
if not client.still_valid():
490
logger.debug(u"Client %(name)s is invalid", vars(client))
494
while sent_size < len(client.secret):
495
sent = session.send(client.secret[sent_size:])
496
logger.debug(u"Sent: %d, remaining: %d",
497
sent, len(client.secret)
498
- (sent_size + sent))
119
503
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
120
__metaclass__ = server_metaclass
121
request_queue_size = 1024
504
"""IPv6 TCP server. Accepts 'None' as address and/or port.
506
settings: Server settings
507
clients: Set() of Client objects
509
address_family = socket.AF_INET6
510
def __init__(self, *args, **kwargs):
511
if "settings" in kwargs:
512
self.settings = kwargs["settings"]
513
del kwargs["settings"]
514
if "clients" in kwargs:
515
self.clients = kwargs["clients"]
516
del kwargs["clients"]
517
return super(type(self), self).__init__(*args, **kwargs)
518
def server_bind(self):
519
"""This overrides the normal server_bind() function
520
to bind to an interface if one was specified, and also NOT to
521
bind to an address or port if they were not specified."""
522
if self.settings["interface"]:
523
# 25 is from /usr/include/asm-i486/socket.h
524
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
526
self.socket.setsockopt(socket.SOL_SOCKET,
528
self.settings["interface"])
529
except socket.error, error:
530
if error[0] == errno.EPERM:
531
logger.warning(u"No permission to"
532
u" bind to interface %s",
533
self.settings["interface"])
536
# Only bind(2) the socket if we really need to.
537
if self.server_address[0] or self.server_address[1]:
538
if not self.server_address[0]:
540
self.server_address = (in6addr_any,
541
self.server_address[1])
542
elif self.server_address[1] is None:
543
self.server_address = (self.server_address[0],
545
return super(type(self), self).server_bind()
126
548
def string_to_delta(interval):
127
549
"""Parse a string and return a datetime.timedelta
582
def server_state_changed(state):
583
"""Derived from the Avahi example code"""
584
if state == avahi.SERVER_COLLISION:
585
logger.warning(u"Server name collision")
587
elif state == avahi.SERVER_RUNNING:
591
def entry_group_state_changed(state, error):
592
"""Derived from the Avahi example code"""
593
logger.debug(u"state change: %i", state)
595
if state == avahi.ENTRY_GROUP_ESTABLISHED:
596
logger.debug(u"Service established.")
597
elif state == avahi.ENTRY_GROUP_COLLISION:
598
logger.warning(u"Service name collision.")
600
elif state == avahi.ENTRY_GROUP_FAILURE:
601
logger.critical(u"Error in group state changed %s",
603
raise AvahiGroupError("State changed: %s", str(error))
605
def if_nametoindex(interface, _func=[None]):
606
"""Call the C function if_nametoindex(), or equivalent"""
607
if _func[0] is not None:
608
return _func[0](interface)
610
if "ctypes.util" not in sys.modules:
614
libc = ctypes.cdll.LoadLibrary\
615
(ctypes.util.find_library("c"))
616
_func[0] = libc.if_nametoindex
617
return _func[0](interface)
621
except (OSError, AttributeError):
622
if "struct" not in sys.modules:
624
if "fcntl" not in sys.modules:
626
def the_hard_way(interface):
627
"Get an interface index the hard way, i.e. using fcntl()"
628
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
630
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
631
struct.pack("16s16x", interface))
633
interface_index = struct.unpack("I", ifreq[16:20])[0]
634
return interface_index
635
_func[0] = the_hard_way
636
return _func[0](interface)
639
def daemon(nochdir, noclose):
640
"""See daemon(3). Standard BSD Unix function.
641
This should really exist as os.daemon, but it doesn't (yet)."""
648
# Close all standard open file descriptors
649
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
650
if not stat.S_ISCHR(os.fstat(null).st_mode):
651
raise OSError(errno.ENODEV,
652
"/dev/null not a character device")
653
os.dup2(null, sys.stdin.fileno())
654
os.dup2(null, sys.stdout.fileno())
655
os.dup2(null, sys.stderr.fileno())
661
global main_loop_started
662
main_loop_started = False
161
664
parser = OptionParser()
162
665
parser.add_option("-i", "--interface", type="string",
163
default="eth0", metavar="IF",
164
help="Interface to bind to")
165
parser.add_option("--cert", type="string", default="cert.pem",
167
help="Public key certificate to use")
168
parser.add_option("--key", type="string", default="key.pem",
170
help="Private key to use")
171
parser.add_option("--ca", type="string", default="ca.pem",
173
help="Certificate Authority certificate to use")
174
parser.add_option("--crl", type="string", default="crl.pem",
176
help="Certificate Revokation List to use")
177
parser.add_option("-p", "--port", type="int", default=49001,
666
metavar="IF", help="Bind to interface IF")
667
parser.add_option("-a", "--address", type="string",
668
help="Address to listen for requests on")
669
parser.add_option("-p", "--port", type="int",
178
670
help="Port number to receive requests on")
179
parser.add_option("--dh", type="int", metavar="BITS",
180
help="DH group to use")
181
parser.add_option("-t", "--timeout", type="string", # Parsed later
183
help="Amount of downtime allowed for clients")
184
parser.add_option("--interval", type="string", # Parsed later
186
help="How often to check that a client is up")
187
671
parser.add_option("--check", action="store_true", default=False,
188
672
help="Run self-test")
673
parser.add_option("--debug", action="store_true", default=False,
674
help="Debug mode; run in foreground and log to"
676
parser.add_option("--priority", type="string", help="GnuTLS"
677
" priority string (see GnuTLS documentation)")
678
parser.add_option("--servicename", type="string", metavar="NAME",
679
help="Zeroconf service name")
680
parser.add_option("--configdir", type="string",
681
default="/etc/mandos", metavar="DIR",
682
help="Directory to search for configuration"
189
684
(options, args) = parser.parse_args()
191
686
if options.check:
193
688
doctest.testmod()
196
# Parse the time arguments
198
options.timeout = string_to_delta(options.timeout)
200
parser.error("option --timeout: Unparseable time")
203
options.interval = string_to_delta(options.interval)
205
parser.error("option --interval: Unparseable time")
207
cert = gnutls.crypto.X509Certificate(open(options.cert).read())
208
key = gnutls.crypto.X509PrivateKey(open(options.key).read())
209
ca = gnutls.crypto.X509Certificate(open(options.ca).read())
210
crl = gnutls.crypto.X509CRL(open(options.crl).read())
211
cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
215
client_config_object = ConfigParser.SafeConfigParser(defaults)
216
client_config_object.read("mandos-clients.conf")
217
clients = [Client(name=section, options=options,
218
**(dict(client_config_object.items(section))))
219
for section in client_config_object.sections()]
221
udp_server = IPv6_UDPServer((in6addr_any, options.port),
225
tcp_server = IPv6_TCPServer((in6addr_any, options.port),
691
# Default values for config file for server-global settings
692
server_defaults = { "interface": "",
697
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
698
"servicename": "Mandos",
701
# Parse config file for server-global settings
702
server_config = ConfigParser.SafeConfigParser(server_defaults)
704
server_config.read(os.path.join(options.configdir, "server.conf"))
705
server_section = "server"
706
# Convert the SafeConfigParser object to a dict
707
server_settings = dict(server_config.items(server_section))
708
# Use getboolean on the boolean config option
709
server_settings["debug"] = server_config.getboolean\
710
(server_section, "debug")
713
# Override the settings from the config file with command line
715
for option in ("interface", "address", "port", "debug",
716
"priority", "servicename", "configdir"):
717
value = getattr(options, option)
718
if value is not None:
719
server_settings[option] = value
721
# Now we have our good server settings in "server_settings"
723
# Parse config file with clients
724
client_defaults = { "timeout": "1h",
726
"checker": "fping -q -- %%(fqdn)s",
728
client_config = ConfigParser.SafeConfigParser(client_defaults)
729
client_config.read(os.path.join(server_settings["configdir"],
733
service = AvahiService(name = server_settings["servicename"],
734
type = "_mandos._tcp", );
735
if server_settings["interface"]:
736
service.interface = if_nametoindex(server_settings["interface"])
741
# From the Avahi example code
742
DBusGMainLoop(set_as_default=True )
743
main_loop = gobject.MainLoop()
744
bus = dbus.SystemBus()
745
server = dbus.Interface(
746
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
747
avahi.DBUS_INTERFACE_SERVER )
748
# End of Avahi example code
750
debug = server_settings["debug"]
753
console = logging.StreamHandler()
754
# console.setLevel(logging.DEBUG)
755
console.setFormatter(logging.Formatter\
756
('%(levelname)s: %(message)s'))
757
logger.addHandler(console)
761
def remove_from_clients(client):
762
clients.remove(client)
764
logger.debug(u"No clients left, exiting")
767
clients.update(Set(Client(name=section,
768
stop_hook = remove_from_clients,
769
**(dict(client_config\
771
for section in client_config.sections()))
777
"Cleanup function; run on exit"
779
# From the Avahi example code
780
if not group is None:
783
# End of Avahi example code
786
client = clients.pop()
787
client.stop_hook = None
790
atexit.register(cleanup)
793
signal.signal(signal.SIGINT, signal.SIG_IGN)
794
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
795
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
797
for client in clients:
800
tcp_server = IPv6_TCPServer((server_settings["address"],
801
server_settings["port"]),
232
in_, out, err = select.select((udp_server,
235
server.handle_request()
238
if __name__ == "__main__":
803
settings=server_settings,
805
# Find out what port we got
806
service.port = tcp_server.socket.getsockname()[1]
807
logger.debug(u"Now listening on address %r, port %d, flowinfo %d,"
808
u" scope_id %d" % tcp_server.socket.getsockname())
810
#service.interface = tcp_server.socket.getsockname()[3]
813
# From the Avahi example code
814
server.connect_to_signal("StateChanged", server_state_changed)
816
server_state_changed(server.GetState())
817
except dbus.exceptions.DBusException, error:
818
logger.critical(u"DBusException: %s", error)
820
# End of Avahi example code
822
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
823
lambda *args, **kwargs:
824
tcp_server.handle_request\
825
(*args[2:], **kwargs) or True)
827
logger.debug("Starting main loop")
828
main_loop_started = True
830
except AvahiError, error:
831
logger.critical(u"AvahiError: %s" + unicode(error))
833
except KeyboardInterrupt:
837
if __name__ == '__main__':