2
# -*- mode: python; coding: utf-8 -*-
4
# Mandos server - give out binary blobs to connecting clients.
6
# This program is partly derived from an example program for an Avahi
7
# service publisher, downloaded from
8
# <http://avahi.org/wiki/PythonPublishExample>. This includes the
9
# methods "add" and "remove" in the "AvahiService" class, the
10
# "server_state_changed" and "entry_group_state_changed" functions,
11
# and some lines in "main".
14
# Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
16
# This program is free software: you can redistribute it and/or modify
17
# it under the terms of the GNU General Public License as published by
18
# the Free Software Foundation, either version 3 of the License, or
19
# (at your option) any later version.
21
# This program is distributed in the hope that it will be useful,
22
# but WITHOUT ANY WARRANTY; without even the implied warranty of
23
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
# GNU General Public License for more details.
26
# You should have received a copy of the GNU General Public License
27
# along with this program. If not, see
28
# <http://www.gnu.org/licenses/>.
30
# Contact the authors at <mandos@fukt.bsnet.se>.
33
from __future__ import division
37
6
from optparse import OptionParser
40
9
import gnutls.crypto
41
10
import gnutls.connection
42
11
import gnutls.errors
43
import gnutls.library.functions
44
import gnutls.library.constants
45
import gnutls.library.types
46
12
import ConfigParser
56
import logging.handlers
62
from dbus.mainloop.glib import DBusGMainLoop
68
logger = logging.Logger('mandos')
69
syslogger = logging.handlers.SysLogHandler\
70
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
72
syslogger.setFormatter(logging.Formatter\
73
('Mandos: %(levelname)s: %(message)s'))
74
logger.addHandler(syslogger)
76
console = logging.StreamHandler()
77
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
79
logger.addHandler(console)
81
class AvahiError(Exception):
82
def __init__(self, value):
84
super(AvahiError, self).__init__()
86
return repr(self.value)
88
class AvahiServiceError(AvahiError):
91
class AvahiGroupError(AvahiError):
95
class AvahiService(object):
96
"""An Avahi (Zeroconf) service.
98
interface: integer; avahi.IF_UNSPEC or an interface index.
99
Used to optionally bind to the specified interface.
100
name: string; Example: 'Mandos'
101
type: string; Example: '_mandos._tcp'.
102
See <http://www.dns-sd.org/ServiceTypes.html>
103
port: integer; what port to announce
104
TXT: list of strings; TXT record for the service
105
domain: string; Domain to publish on, default to .local if empty.
106
host: string; Host to publish records for, default is localhost
107
max_renames: integer; maximum number of renames
108
rename_count: integer; counter so we only rename after collisions
109
a sensible number of times
111
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
112
servicetype = None, port = None, TXT = None, domain = "",
113
host = "", max_renames = 32768):
114
self.interface = interface
116
self.type = servicetype
124
self.rename_count = 0
125
self.max_renames = max_renames
127
"""Derived from the Avahi example code"""
128
if self.rename_count >= self.max_renames:
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
134
logger.info(u"Changing Zeroconf service name to %r ...",
136
syslogger.setFormatter(logging.Formatter\
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
141
self.rename_count += 1
143
"""Derived from the Avahi example code"""
144
if group is not None:
147
"""Derived from the Avahi example code"""
150
group = dbus.Interface\
151
(bus.get_object(avahi.DBUS_NAME,
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
173
15
class Client(object):
174
"""A representation of a client host served by this server.
176
name: string; from the config file, used in log messages
177
fingerprint: string (40 or 32 hexadecimal digits); used to
178
uniquely identify the client
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); object creation, not client host
182
last_checked_ok: datetime.datetime() or None if not yet checked OK
183
timeout: datetime.timedelta(); How long from last_checked_ok
184
until this client is invalid
185
interval: datetime.timedelta(); How often to start a new checker
186
stop_hook: If set, called by stop() as stop_hook(self)
187
checker: subprocess.Popen(); a running checker process used
188
to see if the client lives.
189
'None' if no process is running.
190
checker_initiator_tag: a gobject event source tag, or None
191
stop_initiator_tag: - '' -
192
checker_callback_tag: - '' -
193
checker_command: string; External command which is run to check if
194
client lives. %() expansions are done at
195
runtime with vars(self) as dict, so that for
196
instance %(name)s can be used in the command.
198
_timeout: Real variable for 'timeout'
199
_interval: Real variable for 'interval'
200
_timeout_milliseconds: Used when calling gobject.timeout_add()
201
_interval_milliseconds: - '' -
203
def _set_timeout(self, timeout):
204
"Setter function for 'timeout' attribute"
205
self._timeout = timeout
206
self._timeout_milliseconds = ((self.timeout.days
207
* 24 * 60 * 60 * 1000)
208
+ (self.timeout.seconds * 1000)
209
+ (self.timeout.microseconds
211
timeout = property(lambda self: self._timeout,
214
def _set_interval(self, interval):
215
"Setter function for 'interval' attribute"
216
self._interval = interval
217
self._interval_milliseconds = ((self.interval.days
218
* 24 * 60 * 60 * 1000)
219
+ (self.interval.seconds
221
+ (self.interval.microseconds
223
interval = property(lambda self: self._interval,
226
def __init__(self, name = None, stop_hook=None, config=None):
227
"""Note: the 'checker' key in 'config' sets the
228
'checker_command' attribute and *not* the 'checker'
16
def __init__(self, name=None, dn=None, password=None,
17
passfile=None, fqdn=None, timeout=None,
233
logger.debug(u"Creating client %r", self.name)
234
# Uppercase and remove spaces from fingerprint for later
235
# comparison purposes with return value from the fingerprint()
237
self.fingerprint = config["fingerprint"].upper()\
239
logger.debug(u" Fingerprint: %s", self.fingerprint)
240
if "secret" in config:
241
self.secret = config["secret"].decode(u"base64")
242
elif "secfile" in config:
243
secfile = open(os.path.expanduser(os.path.expandvars
244
(config["secfile"])))
245
self.secret = secfile.read()
22
self.password = password
24
self.password = open(passfile).readall()
248
raise TypeError(u"No secret or secfile for client %s"
250
self.host = config.get("host", "")
26
print "No Password or Passfile in client config file"
27
# raise RuntimeError XXX
28
self.password = "gazonk"
251
30
self.created = datetime.datetime.now()
252
self.last_checked_ok = None
253
self.timeout = string_to_delta(config["timeout"])
254
self.interval = string_to_delta(config["interval"])
255
self.stop_hook = stop_hook
257
self.checker_initiator_tag = None
258
self.stop_initiator_tag = None
259
self.checker_callback_tag = None
260
self.check_command = config["checker"]
262
"""Start this client's checker and timeout hooks"""
263
# Schedule a new checker to be started an 'interval' from now,
264
# and every interval from then on.
265
self.checker_initiator_tag = gobject.timeout_add\
266
(self._interval_milliseconds,
268
# Also start a new checker *right now*.
270
# Schedule a stop() when 'timeout' has passed
271
self.stop_initiator_tag = gobject.timeout_add\
272
(self._timeout_milliseconds,
276
The possibility that a client might be restarted is left open,
277
but not currently used."""
278
# If this client doesn't have a secret, it is already stopped.
279
if hasattr(self, "secret") and self.secret:
280
logger.info(u"Stopping client %s", self.name)
284
if getattr(self, "stop_initiator_tag", False):
285
gobject.source_remove(self.stop_initiator_tag)
286
self.stop_initiator_tag = None
287
if getattr(self, "checker_initiator_tag", False):
288
gobject.source_remove(self.checker_initiator_tag)
289
self.checker_initiator_tag = None
293
# Do not run this again if called by a gobject.timeout_add
296
self.stop_hook = None
298
def checker_callback(self, pid, condition):
299
"""The checker has completed, so take appropriate actions."""
300
now = datetime.datetime.now()
301
self.checker_callback_tag = None
303
if os.WIFEXITED(condition) \
304
and (os.WEXITSTATUS(condition) == 0):
305
logger.info(u"Checker for %(name)s succeeded",
307
self.last_checked_ok = now
308
gobject.source_remove(self.stop_initiator_tag)
309
self.stop_initiator_tag = gobject.timeout_add\
310
(self._timeout_milliseconds,
312
elif not os.WIFEXITED(condition):
313
logger.warning(u"Checker for %(name)s crashed?",
316
logger.info(u"Checker for %(name)s failed",
318
def start_checker(self):
319
"""Start a new checker subprocess if one is not running.
320
If a checker already exists, leave it running and do
322
# The reason for not killing a running checker is that if we
323
# did that, then if a checker (for some reason) started
324
# running slowly and taking more than 'interval' time, the
325
# client would inevitably timeout, since no checker would get
326
# a chance to run to completion. If we instead leave running
327
# checkers alone, the checker would have to take more time
328
# than 'timeout' for the client to be declared invalid, which
329
# is as it should be.
330
if self.checker is None:
332
# In case check_command has exactly one % operator
333
command = self.check_command % self.host
335
# Escape attributes for the shell
336
escaped_attrs = dict((key, re.escape(str(val)))
338
vars(self).iteritems())
340
command = self.check_command % escaped_attrs
341
except TypeError, error:
342
logger.error(u'Could not format string "%s":'
343
u' %s', self.check_command, error)
344
return True # Try again later
346
logger.info(u"Starting checker %r for %s",
348
# We don't need to redirect stdout and stderr, since
349
# in normal mode, that is already done by daemon(),
350
# and in debug mode we don't want to. (Stdin is
351
# always replaced by /dev/null.)
352
self.checker = subprocess.Popen(command,
355
self.checker_callback_tag = gobject.child_watch_add\
357
self.checker_callback)
358
except OSError, error:
359
logger.error(u"Failed to start subprocess: %s",
361
# Re-run this periodically if run by gobject.timeout_add
363
def stop_checker(self):
364
"""Force the checker process, if any, to stop."""
365
if self.checker_callback_tag:
366
gobject.source_remove(self.checker_callback_tag)
367
self.checker_callback_tag = None
368
if getattr(self, "checker", None) is None:
370
logger.debug(u"Stopping checker for %(name)s", vars(self))
372
os.kill(self.checker.pid, signal.SIGTERM)
374
#if self.checker.poll() is None:
375
# os.kill(self.checker.pid, signal.SIGKILL)
376
except OSError, error:
377
if error.errno != errno.ESRCH: # No such process
380
def still_valid(self):
381
"""Has the timeout not yet passed for this client?"""
382
now = datetime.datetime.now()
383
if self.last_checked_ok is None:
384
return now < (self.created + self.timeout)
386
return now < (self.last_checked_ok + self.timeout)
389
def peer_certificate(session):
390
"Return the peer's OpenPGP certificate as a bytestring"
391
# If not an OpenPGP certificate...
392
if gnutls.library.functions.gnutls_certificate_type_get\
393
(session._c_object) \
394
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
395
# ...do the normal thing
396
return session.peer_certificate
397
list_size = ctypes.c_uint()
398
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
399
(session._c_object, ctypes.byref(list_size))
400
if list_size.value == 0:
403
return ctypes.string_at(cert.data, cert.size)
406
def fingerprint(openpgp):
407
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
408
# New GnuTLS "datum" with the OpenPGP public key
409
datum = gnutls.library.types.gnutls_datum_t\
410
(ctypes.cast(ctypes.c_char_p(openpgp),
411
ctypes.POINTER(ctypes.c_ubyte)),
412
ctypes.c_uint(len(openpgp)))
413
# New empty GnuTLS certificate
414
crt = gnutls.library.types.gnutls_openpgp_crt_t()
415
gnutls.library.functions.gnutls_openpgp_crt_init\
417
# Import the OpenPGP public key into the certificate
418
gnutls.library.functions.gnutls_openpgp_crt_import\
419
(crt, ctypes.byref(datum),
420
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
421
# Verify the self signature in the key
422
crtverify = ctypes.c_uint()
423
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
424
(crt, 0, ctypes.byref(crtverify))
425
if crtverify.value != 0:
426
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
427
raise gnutls.errors.CertificateSecurityError("Verify failed")
428
# New buffer for the fingerprint
429
buf = ctypes.create_string_buffer(20)
430
buf_len = ctypes.c_size_t()
431
# Get the fingerprint from the certificate into the buffer
432
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
433
(crt, ctypes.byref(buf), ctypes.byref(buf_len))
434
# Deinit the certificate
435
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
436
# Convert the buffer to a Python bytestring
437
fpr = ctypes.string_at(buf, buf_len.value)
438
# Convert the bytestring to hexadecimal notation
439
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
443
class TCP_handler(SocketServer.BaseRequestHandler, object):
444
"""A TCP request handler class.
445
Instantiated by IPv6_TCPServer for each request to handle it.
446
Note: This will run in its own forked process."""
449
logger.info(u"TCP connection from: %s",
450
unicode(self.client_address))
451
session = gnutls.connection.ClientSession\
452
(self.request, gnutls.connection.X509Credentials())
454
line = self.request.makefile().readline()
455
logger.debug(u"Protocol version: %r", line)
457
if int(line.strip().split()[0]) > 1:
459
except (ValueError, IndexError, RuntimeError), error:
460
logger.error(u"Unknown protocol version: %s", error)
463
# Note: gnutls.connection.X509Credentials is really a generic
464
# GnuTLS certificate credentials object so long as no X.509
465
# keys are added to it. Therefore, we can use it here despite
466
# using OpenPGP certificates.
468
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
469
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
471
priority = "NORMAL" # Fallback default, since this
473
if self.server.settings["priority"]:
474
priority = self.server.settings["priority"]
475
gnutls.library.functions.gnutls_priority_set_direct\
476
(session._c_object, priority, None)
480
except gnutls.errors.GNUTLSError, error:
481
logger.warning(u"Handshake failed: %s", error)
482
# Do not run session.bye() here: the session is not
483
# established. Just abandon the request.
486
fpr = fingerprint(peer_certificate(session))
487
except (TypeError, gnutls.errors.GNUTLSError), error:
488
logger.warning(u"Bad certificate: %s", error)
491
logger.debug(u"Fingerprint: %s", fpr)
493
for c in self.server.clients:
494
if c.fingerprint == fpr:
498
logger.warning(u"Client not found for fingerprint: %s",
502
# Have to check if client.still_valid(), since it is possible
503
# that the client timed out while establishing the GnuTLS
505
if not client.still_valid():
506
logger.warning(u"Client %(name)s is invalid",
511
while sent_size < len(client.secret):
512
sent = session.send(client.secret[sent_size:])
513
logger.debug(u"Sent: %d, remaining: %d",
514
sent, len(client.secret)
515
- (sent_size + sent))
33
timeout = self.server.options.timeout
34
self.timeout = timeout
36
interval = self.server.options.interval
37
self.interval = interval
38
self.next_check = datetime.datetime.now()
40
def server_bind(self):
41
if self.options.interface:
42
if not hasattr(socket, "SO_BINDTODEVICE"):
43
# From /usr/include/asm-i486/socket.h
44
socket.SO_BINDTODEVICE = 25
46
self.socket.setsockopt(socket.SOL_SOCKET,
47
socket.SO_BINDTODEVICE,
48
self.options.interface)
49
except socket.error, error:
50
if error[0] == errno.EPERM:
51
print "Warning: Denied permission to bind to interface", \
52
self.options.interface
55
return super(type(self), self).server_bind()
58
def init_with_options(self, *args, **kwargs):
59
if "options" in kwargs:
60
self.options = kwargs["options"]
62
if "clients" in kwargs:
63
self.clients = kwargs["clients"]
65
if "credentials" in kwargs:
66
self.credentials = kwargs["credentials"]
67
del kwargs["credentials"]
68
return super(type(self), self).__init__(*args, **kwargs)
71
class udp_handler(SocketServer.DatagramRequestHandler, object):
73
self.wfile.write("Polo")
74
print "UDP request answered"
77
class IPv6_UDPServer(SocketServer.UDPServer, object):
78
__init__ = init_with_options
79
address_family = socket.AF_INET6
80
allow_reuse_address = True
81
server_bind = server_bind
82
def verify_request(self, request, client_address):
83
print "UDP request came"
84
return request[0] == "Marco"
87
class tcp_handler(SocketServer.BaseRequestHandler, object):
89
print "TCP request came"
90
print "Request:", self.request
91
print "Client Address:", self.client_address
92
print "Server:", self.server
93
session = gnutls.connection.ServerSession(self.request,
94
self.server.credentials)
96
if session.peer_certificate:
97
print "DN:", session.peer_certificate.subject
100
except gnutls.errors.CertificateError, error:
101
print "Verify failed", error
105
session.send(dict((client.dn, client.password)
106
for client in self.server.clients)
107
[session.peer_certificate.subject])
109
session.send("gazonk")
520
113
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
521
"""IPv6 TCP server. Accepts 'None' as address and/or port.
523
settings: Server settings
524
clients: Set() of Client objects
525
enabled: Boolean; whether this server is activated yet
114
__init__ = init_with_options
527
115
address_family = socket.AF_INET6
528
def __init__(self, *args, **kwargs):
529
if "settings" in kwargs:
530
self.settings = kwargs["settings"]
531
del kwargs["settings"]
532
if "clients" in kwargs:
533
self.clients = kwargs["clients"]
534
del kwargs["clients"]
536
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
537
def server_bind(self):
538
"""This overrides the normal server_bind() function
539
to bind to an interface if one was specified, and also NOT to
540
bind to an address or port if they were not specified."""
541
if self.settings["interface"]:
542
# 25 is from /usr/include/asm-i486/socket.h
543
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
545
self.socket.setsockopt(socket.SOL_SOCKET,
547
self.settings["interface"])
548
except socket.error, error:
549
if error[0] == errno.EPERM:
550
logger.error(u"No permission to"
551
u" bind to interface %s",
552
self.settings["interface"])
555
# Only bind(2) the socket if we really need to.
556
if self.server_address[0] or self.server_address[1]:
557
if not self.server_address[0]:
559
self.server_address = (in6addr_any,
560
self.server_address[1])
561
elif not self.server_address[1]:
562
self.server_address = (self.server_address[0],
564
# if self.settings["interface"]:
565
# self.server_address = (self.server_address[0],
571
return super(IPv6_TCPServer, self).server_bind()
572
def server_activate(self):
574
return super(IPv6_TCPServer, self).server_activate()
116
allow_reuse_address = True
117
request_queue_size = 1024
118
server_bind = server_bind
579
125
def string_to_delta(interval):
580
126
"""Parse a string and return a datetime.timedelta
589
135
datetime.timedelta(1)
590
136
>>> string_to_delta(u'1w')
591
137
datetime.timedelta(7)
592
>>> string_to_delta('5m 30s')
593
datetime.timedelta(0, 330)
595
timevalue = datetime.timedelta(0)
596
for s in interval.split():
598
suffix = unicode(s[-1])
601
delta = datetime.timedelta(value)
603
delta = datetime.timedelta(0, value)
605
delta = datetime.timedelta(0, 0, 0, 0, value)
607
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
609
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
612
except (ValueError, IndexError):
140
suffix=unicode(interval[-1])
141
value=int(interval[:-1])
143
delta = datetime.timedelta(value)
145
delta = datetime.timedelta(0, value)
147
delta = datetime.timedelta(0, 0, 0, 0, value)
149
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
151
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
618
def server_state_changed(state):
619
"""Derived from the Avahi example code"""
620
if state == avahi.SERVER_COLLISION:
621
logger.error(u"Zeroconf server name collision")
623
elif state == avahi.SERVER_RUNNING:
627
def entry_group_state_changed(state, error):
628
"""Derived from the Avahi example code"""
629
logger.debug(u"Avahi state change: %i", state)
631
if state == avahi.ENTRY_GROUP_ESTABLISHED:
632
logger.debug(u"Zeroconf service established.")
633
elif state == avahi.ENTRY_GROUP_COLLISION:
634
logger.warning(u"Zeroconf service name collision.")
636
elif state == avahi.ENTRY_GROUP_FAILURE:
637
logger.critical(u"Avahi: Error in group state changed %s",
639
raise AvahiGroupError("State changed: %s", str(error))
641
def if_nametoindex(interface):
642
"""Call the C function if_nametoindex(), or equivalent"""
643
global if_nametoindex
645
if_nametoindex = ctypes.cdll.LoadLibrary\
646
(ctypes.util.find_library("c")).if_nametoindex
647
except (OSError, AttributeError):
648
if "struct" not in sys.modules:
650
if "fcntl" not in sys.modules:
652
def if_nametoindex(interface):
653
"Get an interface index the hard way, i.e. using fcntl()"
654
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
656
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
657
struct.pack("16s16x", interface))
659
interface_index = struct.unpack("I", ifreq[16:20])[0]
660
return interface_index
661
return if_nametoindex(interface)
664
def daemon(nochdir = False, noclose = False):
665
"""See daemon(3). Standard BSD Unix function.
666
This should really exist as os.daemon, but it doesn't (yet)."""
675
# Close all standard open file descriptors
676
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
677
if not stat.S_ISCHR(os.fstat(null).st_mode):
678
raise OSError(errno.ENODEV,
679
"/dev/null not a character device")
680
os.dup2(null, sys.stdin.fileno())
681
os.dup2(null, sys.stdout.fileno())
682
os.dup2(null, sys.stderr.fileno())
154
except (ValueError, IndexError):
688
parser = OptionParser(version = "%%prog %s" % version)
159
parser = OptionParser()
689
160
parser.add_option("-i", "--interface", type="string",
690
metavar="IF", help="Bind to interface IF")
691
parser.add_option("-a", "--address", type="string",
692
help="Address to listen for requests on")
693
parser.add_option("-p", "--port", type="int",
161
default="eth0", metavar="IF",
162
help="Interface to bind to")
163
parser.add_option("--cert", type="string", default="cert.pem",
165
help="Public key certificate to use")
166
parser.add_option("--key", type="string", default="key.pem",
168
help="Private key to use")
169
parser.add_option("--ca", type="string", default="ca.pem",
171
help="Certificate Authority certificate to use")
172
parser.add_option("--crl", type="string", default="crl.pem",
174
help="Certificate Revokation List to use")
175
parser.add_option("-p", "--port", type="int", default=49001,
694
176
help="Port number to receive requests on")
177
parser.add_option("--dh", type="int", metavar="BITS",
178
help="DH group to use")
179
parser.add_option("-t", "--timeout", type="string", # Parsed later
181
help="Amount of downtime allowed for clients")
182
parser.add_option("--interval", type="string", # Parsed later
184
help="How often to check that a client is up")
695
185
parser.add_option("--check", action="store_true", default=False,
696
186
help="Run self-test")
697
parser.add_option("--debug", action="store_true",
698
help="Debug mode; run in foreground and log to"
700
parser.add_option("--priority", type="string", help="GnuTLS"
701
" priority string (see GnuTLS documentation)")
702
parser.add_option("--servicename", type="string", metavar="NAME",
703
help="Zeroconf service name")
704
parser.add_option("--configdir", type="string",
705
default="/etc/mandos", metavar="DIR",
706
help="Directory to search for configuration"
708
options = parser.parse_args()[0]
187
(options, args) = parser.parse_args()
710
189
if options.check:
712
191
doctest.testmod()
715
# Default values for config file for server-global settings
716
server_defaults = { "interface": "",
721
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
722
"servicename": "Mandos",
725
# Parse config file for server-global settings
726
server_config = ConfigParser.SafeConfigParser(server_defaults)
728
server_config.read(os.path.join(options.configdir, "mandos.conf"))
729
# Convert the SafeConfigParser object to a dict
730
server_settings = server_config.defaults()
731
# Use getboolean on the boolean config option
732
server_settings["debug"] = server_config.getboolean\
736
# Override the settings from the config file with command line
738
for option in ("interface", "address", "port", "debug",
739
"priority", "servicename", "configdir"):
740
value = getattr(options, option)
741
if value is not None:
742
server_settings[option] = value
744
# Now we have our good server settings in "server_settings"
746
debug = server_settings["debug"]
749
syslogger.setLevel(logging.WARNING)
750
console.setLevel(logging.WARNING)
752
if server_settings["servicename"] != "Mandos":
753
syslogger.setFormatter(logging.Formatter\
754
('Mandos (%s): %%(levelname)s:'
756
% server_settings["servicename"]))
758
# Parse config file with clients
759
client_defaults = { "timeout": "1h",
761
"checker": "fping -q -- %(host)s",
764
client_config = ConfigParser.SafeConfigParser(client_defaults)
765
client_config.read(os.path.join(server_settings["configdir"],
769
tcp_server = IPv6_TCPServer((server_settings["address"],
770
server_settings["port"]),
772
settings=server_settings,
774
pidfilename = "/var/run/mandos.pid"
776
pidfile = open(pidfilename, "w")
777
except IOError, error:
778
logger.error("Could not open file %r", pidfilename)
783
uid = pwd.getpwnam("mandos").pw_uid
786
uid = pwd.getpwnam("nobody").pw_uid
790
gid = pwd.getpwnam("mandos").pw_gid
793
gid = pwd.getpwnam("nogroup").pw_gid
799
except OSError, error:
800
if error[0] != errno.EPERM:
804
service = AvahiService(name = server_settings["servicename"],
805
servicetype = "_mandos._tcp", )
806
if server_settings["interface"]:
807
service.interface = if_nametoindex\
808
(server_settings["interface"])
813
# From the Avahi example code
814
DBusGMainLoop(set_as_default=True )
815
main_loop = gobject.MainLoop()
816
bus = dbus.SystemBus()
817
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
818
avahi.DBUS_PATH_SERVER),
819
avahi.DBUS_INTERFACE_SERVER)
820
# End of Avahi example code
822
def remove_from_clients(client):
823
clients.remove(client)
825
logger.critical(u"No clients left, exiting")
828
clients.update(Set(Client(name = section,
829
stop_hook = remove_from_clients,
831
= dict(client_config.items(section)))
832
for section in client_config.sections()))
834
logger.critical(u"No clients defined")
838
# Redirect stdin so all checkers get /dev/null
839
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
840
os.dup2(null, sys.stdin.fileno())
845
logger.removeHandler(console)
846
# Close all input and output, do double fork, etc.
851
pidfile.write(str(pid) + "\n")
855
logger.error(u"Could not write to file %r with PID %d",
858
# "pidfile" was never created
863
"Cleanup function; run on exit"
865
# From the Avahi example code
866
if not group is None:
869
# End of Avahi example code
872
client = clients.pop()
873
client.stop_hook = None
876
atexit.register(cleanup)
879
signal.signal(signal.SIGINT, signal.SIG_IGN)
880
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
881
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
883
for client in clients:
887
tcp_server.server_activate()
889
# Find out what port we got
890
service.port = tcp_server.socket.getsockname()[1]
891
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
892
u" scope_id %d" % tcp_server.socket.getsockname())
894
#service.interface = tcp_server.socket.getsockname()[3]
897
# From the Avahi example code
898
server.connect_to_signal("StateChanged", server_state_changed)
900
server_state_changed(server.GetState())
901
except dbus.exceptions.DBusException, error:
902
logger.critical(u"DBusException: %s", error)
904
# End of Avahi example code
906
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
907
lambda *args, **kwargs:
908
tcp_server.handle_request\
909
(*args[2:], **kwargs) or True)
911
logger.debug(u"Starting main loop")
913
except AvahiError, error:
914
logger.critical(u"AvahiError: %s" + unicode(error))
916
except KeyboardInterrupt:
920
if __name__ == '__main__':
194
# Parse the time arguments
196
options.timeout = string_to_delta(options.timeout)
198
parser.error("option --timeout: Unparseable time")
201
options.interval = string_to_delta(options.interval)
203
parser.error("option --interval: Unparseable time")
205
cert = gnutls.crypto.X509Certificate(open(options.cert).read())
206
key = gnutls.crypto.X509PrivateKey(open(options.key).read())
207
ca = gnutls.crypto.X509Certificate(open(options.ca).read())
208
crl = gnutls.crypto.X509CRL(open(options.crl).read())
209
cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
213
client_config_object = ConfigParser.SafeConfigParser(defaults)
214
client_config_object.read("mandos-clients.conf")
215
clients = [Client(name=section,
216
**(dict(client_config_object.items(section))))
217
for section in client_config_object.sections()]
219
udp_server = IPv6_UDPServer((in6addr_any, options.port),
223
tcp_server = IPv6_TCPServer((in6addr_any, options.port),
230
in_, out, err = select.select((udp_server,
233
server.handle_request()
236
if __name__ == "__main__":