52
22
from sets import Set
57
import logging.handlers
59
from contextlib import closing
65
28
from dbus.mainloop.glib import DBusGMainLoop
71
logger = logging.Logger('mandos')
72
syslogger = (logging.handlers.SysLogHandler
73
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
74
address = "/dev/log"))
75
syslogger.setFormatter(logging.Formatter
76
('Mandos: %(levelname)s: %(message)s'))
77
logger.addHandler(syslogger)
79
console = logging.StreamHandler()
80
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
82
logger.addHandler(console)
84
class AvahiError(Exception):
85
def __init__(self, value):
87
super(AvahiError, self).__init__()
89
return repr(self.value)
91
class AvahiServiceError(AvahiError):
94
class AvahiGroupError(AvahiError):
98
class AvahiService(object):
99
"""An Avahi (Zeroconf) service.
101
interface: integer; avahi.IF_UNSPEC or an interface index.
102
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
114
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
115
servicetype = None, port = None, TXT = None,
116
domain = "", host = "", max_renames = 32768):
117
self.interface = interface
119
self.type = servicetype
121
self.TXT = TXT if TXT is not None else []
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(bus.get_object
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
31
# This variable is used to optionally bind to a specified interface.
32
# It is a global variable to fit in with the other variables from the
33
# Avahi server example code.
34
serviceInterface = avahi.IF_UNSPEC
35
# From the Avahi server example code:
36
serviceName = "Mandos"
37
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
38
servicePort = None # Not known at startup
39
serviceTXT = [] # TXT record for the service
40
domain = "" # Domain to publish on, default to .local
41
host = "" # Host to publish records for, default to localhost
42
group = None #our entry group
43
rename_count = 12 # Counter so we only rename after collisions a
44
# sensible number of times
170
45
# End of Avahi example code
173
class Client(dbus.service.Object):
174
49
"""A representation of a client host served by this server.
176
51
name: string; from the config file, used in log messages
177
52
fingerprint: string (40 or 32 hexadecimal digits); used to
178
53
uniquely identify the client
179
54
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); (UTC) object creation
182
started: datetime.datetime(); (UTC) last started
183
last_checked_ok: datetime.datetime(); (UTC) or None
184
timeout: datetime.timedelta(); How long from last_checked_ok
185
until this client is invalid
55
fqdn: string (FQDN); available for use by the checker command
56
created: datetime.datetime()
57
last_seen: datetime.datetime() or None if not yet seen
58
timeout: datetime.timedelta(); How long from last_seen until
59
this client is invalid
186
60
interval: datetime.timedelta(); How often to start a new checker
187
61
stop_hook: If set, called by stop() as stop_hook(self)
188
62
checker: subprocess.Popen(); a running checker process used
189
63
to see if the client lives.
190
'None' if no process is running.
64
Is None if no process is running.
191
65
checker_initiator_tag: a gobject event source tag, or None
192
66
stop_initiator_tag: - '' -
193
67
checker_callback_tag: - '' -
194
68
checker_command: string; External command which is run to check if
195
client lives. %() expansions are done at
69
client lives. %()s expansions are done at
196
70
runtime with vars(self) as dict, so that for
197
71
instance %(name)s can be used in the command.
198
72
Private attibutes:
199
73
_timeout: Real variable for 'timeout'
200
74
_interval: Real variable for 'interval'
201
_timeout_milliseconds: Used when calling gobject.timeout_add()
75
_timeout_milliseconds: Used by gobject.timeout_add()
202
76
_interval_milliseconds: - '' -
204
78
def _set_timeout(self, timeout):
205
"Setter function for the 'timeout' attribute"
79
"Setter function for 'timeout' attribute"
206
80
self._timeout = timeout
207
81
self._timeout_milliseconds = ((self.timeout.days
208
82
* 24 * 60 * 60 * 1000)
209
83
+ (self.timeout.seconds * 1000)
210
84
+ (self.timeout.microseconds
213
self.TimeoutChanged(self._timeout_milliseconds)
214
timeout = property(lambda self: self._timeout, _set_timeout)
86
timeout = property(lambda self: self._timeout,
217
89
def _set_interval(self, interval):
218
"Setter function for the 'interval' attribute"
90
"Setter function for 'interval' attribute"
219
91
self._interval = interval
220
92
self._interval_milliseconds = ((self.interval.days
221
93
* 24 * 60 * 60 * 1000)
224
96
+ (self.interval.microseconds
227
self.IntervalChanged(self._interval_milliseconds)
228
interval = property(lambda self: self._interval, _set_interval)
98
interval = property(lambda self: self._interval,
229
100
del _set_interval
231
def __init__(self, name = None, stop_hook=None, config=None):
232
"""Note: the 'checker' key in 'config' sets the
233
'checker_command' attribute and *not* the 'checker'
235
dbus.service.Object.__init__(self, bus,
237
% name.replace(".", "_"))
101
def __init__(self, name=None, options=None, stop_hook=None,
102
fingerprint=None, secret=None, secfile=None, fqdn=None,
103
timeout=None, interval=-1, checker=None):
241
logger.debug(u"Creating client %r", self.name)
242
# Uppercase and remove spaces from fingerprint for later
243
# comparison purposes with return value from the fingerprint()
245
self.fingerprint = (config["fingerprint"].upper()
247
logger.debug(u" Fingerprint: %s", self.fingerprint)
248
if "secret" in config:
249
self.secret = config["secret"].decode(u"base64")
250
elif "secfile" in config:
251
with closing(open(os.path.expanduser
253
(config["secfile"])))) as secfile:
254
self.secret = secfile.read()
256
raise TypeError(u"No secret or secfile for client %s"
258
self.host = config.get("host", "")
259
self.created = datetime.datetime.utcnow()
261
self.last_checked_ok = None
262
self.timeout = string_to_delta(config["timeout"])
263
self.interval = string_to_delta(config["interval"])
105
# Uppercase and remove spaces from fingerprint
106
# for later comparison purposes with return value of
107
# the fingerprint() function
108
self.fingerprint = fingerprint.upper().replace(u" ", u"")
110
self.secret = secret.decode(u"base64")
113
self.secret = sf.read()
116
raise RuntimeError(u"No secret or secfile for client %s"
118
self.fqdn = fqdn # string
119
self.created = datetime.datetime.now()
120
self.last_seen = None
122
timeout = options.timeout
123
self.timeout = timeout
125
interval = options.interval
127
interval = string_to_delta(interval)
128
self.interval = interval
264
129
self.stop_hook = stop_hook
265
130
self.checker = None
266
131
self.checker_initiator_tag = None
267
132
self.stop_initiator_tag = None
268
133
self.checker_callback_tag = None
269
self.check_command = config["checker"]
134
self.check_command = checker
272
"""Start this client's checker and timeout hooks"""
273
self.started = datetime.datetime.utcnow()
136
"""Start this clients checker and timeout hooks"""
274
137
# Schedule a new checker to be started an 'interval' from now,
275
138
# and every interval from then on.
276
self.checker_initiator_tag = (gobject.timeout_add
277
(self._interval_milliseconds,
139
self.checker_initiator_tag = gobject.timeout_add\
140
(self._interval_milliseconds,
279
142
# Also start a new checker *right now*.
280
143
self.start_checker()
281
144
# Schedule a stop() when 'timeout' has passed
282
self.stop_initiator_tag = (gobject.timeout_add
283
(self._timeout_milliseconds,
286
self.StateChanged(True)
145
self.stop_initiator_tag = gobject.timeout_add\
146
(self._timeout_milliseconds,
289
"""Stop this client."""
290
if getattr(self, "started", None) is not None:
291
logger.info(u"Stopping client %s", self.name)
294
if getattr(self, "stop_initiator_tag", False):
150
The possibility that this client might be restarted is left
151
open, but not currently used."""
153
sys.stderr.write(u"Stopping client %s\n" % self.name)
155
if self.stop_initiator_tag:
295
156
gobject.source_remove(self.stop_initiator_tag)
296
157
self.stop_initiator_tag = None
297
if getattr(self, "checker_initiator_tag", False):
158
if self.checker_initiator_tag:
298
159
gobject.source_remove(self.checker_initiator_tag)
299
160
self.checker_initiator_tag = None
300
161
self.stop_checker()
301
162
if self.stop_hook:
302
163
self.stop_hook(self)
305
self.StateChanged(False)
306
164
# Do not run this again if called by a gobject.timeout_add
309
166
def __del__(self):
310
self.stop_hook = None
167
# Some code duplication here and in stop()
168
if hasattr(self, "stop_initiator_tag") \
169
and self.stop_initiator_tag:
170
gobject.source_remove(self.stop_initiator_tag)
171
self.stop_initiator_tag = None
172
if hasattr(self, "checker_initiator_tag") \
173
and self.checker_initiator_tag:
174
gobject.source_remove(self.checker_initiator_tag)
175
self.checker_initiator_tag = None
313
177
def checker_callback(self, pid, condition):
314
178
"""The checker has completed, so take appropriate actions."""
179
now = datetime.datetime.now()
180
if os.WIFEXITED(condition) \
181
and (os.WEXITSTATUS(condition) == 0):
183
sys.stderr.write(u"Checker for %(name)s succeeded\n"
186
gobject.source_remove(self.stop_initiator_tag)
187
self.stop_initiator_tag = gobject.timeout_add\
188
(self._timeout_milliseconds,
191
if not os.WIFEXITED(condition):
192
sys.stderr.write(u"Checker for %(name)s crashed?\n"
195
sys.stderr.write(u"Checker for %(name)s failed\n"
315
198
self.checker_callback_tag = None
317
if (os.WIFEXITED(condition)
318
and (os.WEXITSTATUS(condition) == 0)):
319
logger.info(u"Checker for %(name)s succeeded",
322
self.CheckerCompleted(True)
324
elif not os.WIFEXITED(condition):
325
logger.warning(u"Checker for %(name)s crashed?",
328
self.CheckerCompleted(False)
330
logger.info(u"Checker for %(name)s failed",
333
self.CheckerCompleted(False)
335
def bump_timeout(self):
336
"""Bump up the timeout for this client.
337
This should only be called when the client has been seen,
340
self.last_checked_ok = datetime.datetime.utcnow()
341
gobject.source_remove(self.stop_initiator_tag)
342
self.stop_initiator_tag = (gobject.timeout_add
343
(self._timeout_milliseconds,
346
199
def start_checker(self):
347
200
"""Start a new checker subprocess if one is not running.
348
201
If a checker already exists, leave it running and do
350
# The reason for not killing a running checker is that if we
351
# did that, then if a checker (for some reason) started
352
# running slowly and taking more than 'interval' time, the
353
# client would inevitably timeout, since no checker would get
354
# a chance to run to completion. If we instead leave running
355
# checkers alone, the checker would have to take more time
356
# than 'timeout' for the client to be declared invalid, which
357
# is as it should be.
358
203
if self.checker is None:
205
sys.stderr.write(u"Starting checker for %s\n"
360
# In case check_command has exactly one % operator
361
command = self.check_command % self.host
208
command = self.check_command % self.fqdn
362
209
except TypeError:
363
# Escape attributes for the shell
364
210
escaped_attrs = dict((key, re.escape(str(val)))
366
212
vars(self).iteritems())
368
command = self.check_command % escaped_attrs
369
except TypeError, error:
370
logger.error(u'Could not format string "%s":'
371
u' %s', self.check_command, error)
372
return True # Try again later
213
command = self.check_command % escaped_attrs
374
logger.info(u"Starting checker %r for %s",
376
# We don't need to redirect stdout and stderr, since
377
# in normal mode, that is already done by daemon(),
378
# and in debug mode we don't want to. (Stdin is
379
# always replaced by /dev/null.)
380
self.checker = subprocess.Popen(command,
383
self.checker_callback_tag = (gobject.child_watch_add
385
self.checker_callback))
387
self.CheckerStarted(command)
388
except OSError, error:
389
logger.error(u"Failed to start subprocess: %s",
215
self.checker = subprocess.\
217
stdout=subprocess.PIPE,
218
close_fds=True, shell=True,
220
self.checker_callback_tag = gobject.\
221
child_watch_add(self.checker.pid,
224
except subprocess.OSError, error:
225
sys.stderr.write(u"Failed to start subprocess: %s\n"
391
227
# Re-run this periodically if run by gobject.timeout_add
394
229
def stop_checker(self):
395
230
"""Force the checker process, if any, to stop."""
396
if self.checker_callback_tag:
397
gobject.source_remove(self.checker_callback_tag)
398
self.checker_callback_tag = None
399
if getattr(self, "checker", None) is None:
231
if not hasattr(self, "checker") or self.checker is None:
401
logger.debug(u"Stopping checker for %(name)s", vars(self))
403
os.kill(self.checker.pid, signal.SIGTERM)
405
#if self.checker.poll() is None:
406
# os.kill(self.checker.pid, signal.SIGKILL)
407
except OSError, error:
408
if error.errno != errno.ESRCH: # No such process
233
gobject.source_remove(self.checker_callback_tag)
234
self.checker_callback_tag = None
235
os.kill(self.checker.pid, signal.SIGTERM)
236
if self.checker.poll() is None:
237
os.kill(self.checker.pid, signal.SIGKILL)
410
238
self.checker = None
412
def still_valid(self):
239
def still_valid(self, now=None):
413
240
"""Has the timeout not yet passed for this client?"""
416
now = datetime.datetime.utcnow()
417
if self.last_checked_ok is None:
242
now = datetime.datetime.now()
243
if self.last_seen is None:
418
244
return now < (self.created + self.timeout)
420
return now < (self.last_checked_ok + self.timeout)
422
## D-Bus methods & signals
423
_interface = u"org.mandos_system.Mandos.Client"
425
def _datetime_to_dbus_struct(dt):
426
return dbus.Struct(dt.year, dt.month, dt.day, dt.hour,
427
dt.minute, dt.second, dt.microsecond,
430
# BumpTimeout - method
431
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
BumpTimeout.__name__ = "BumpTimeout"
434
# IntervalChanged - signal
435
@dbus.service.signal(_interface, signature="t")
436
def IntervalChanged(self, t):
440
# CheckerCompleted - signal
441
@dbus.service.signal(_interface, signature="b")
442
def CheckerCompleted(self, success):
446
# CheckerIsRunning - method
447
@dbus.service.method(_interface, out_signature="b")
448
def CheckerIsRunning(self):
449
"D-Bus getter method"
450
return self.checker is not None
452
# CheckerStarted - signal
453
@dbus.service.signal(_interface, signature="s")
454
def CheckerStarted(self, command):
458
# GetChecker - method
459
@dbus.service.method(_interface, out_signature="s")
460
def GetChecker(self):
461
"D-Bus getter method"
462
return self.checker_command
464
# GetCreated - method
465
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
466
def GetCreated(self):
467
"D-Bus getter method"
468
return datetime_to_dbus_struct(self.created)
470
# GetFingerprint - method
471
@dbus.service.method(_interface, out_signature="s")
472
def GetFingerprint(self):
473
"D-Bus getter method"
474
return self.fingerprint
477
@dbus.service.method(_interface, out_signature="s")
479
"D-Bus getter method"
482
# GetInterval - method
483
@dbus.service.method(_interface, out_signature="t")
484
def GetInterval(self):
485
"D-Bus getter method"
486
return self._interval_milliseconds
489
@dbus.service.method(_interface, out_signature="s")
491
"D-Bus getter method"
494
# GetStarted - method
495
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
496
def GetStarted(self):
497
"D-Bus getter method"
498
if self.started is not None:
499
return datetime_to_dbus_struct(self.started)
501
return dbus.Struct(0, 0, 0, 0, 0, 0, 0,
504
# GetTimeout - method
505
@dbus.service.method(_interface, out_signature="t")
506
def GetTimeout(self):
507
"D-Bus getter method"
508
return self._timeout_milliseconds
510
# SetChecker - method
511
@dbus.service.method(_interface, in_signature="s")
512
def SetChecker(self, checker):
513
"D-Bus setter method"
514
self.checker_command = checker
517
@dbus.service.method(_interface, in_signature="s")
518
def SetHost(self, host):
519
"D-Bus setter method"
522
# SetInterval - method
523
@dbus.service.method(_interface, in_signature="t")
524
def SetInterval(self, milliseconds):
525
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
527
# SetTimeout - method
528
@dbus.service.method(_interface, in_signature="t")
529
def SetTimeout(self, milliseconds):
530
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
533
@dbus.service.method(_interface, in_signature="ay",
535
def SetSecret(self, secret):
536
"D-Bus setter method"
537
self.secret = str(secret)
540
Start = dbus.service.method(_interface)(start)
541
Start.__name__ = "Start"
543
# StartChecker - method
544
StartChecker = dbus.service.method(_interface)(start_checker)
545
StartChecker.__name__ = "StartChecker"
547
# StateChanged - signal
548
@dbus.service.signal(_interface, signature="b")
549
def StateChanged(self, started):
553
# StillValid - method
554
StillValid = (dbus.service.method(_interface, out_signature="b")
556
StillValid.__name__ = "StillValid"
559
Stop = dbus.service.method(_interface)(stop)
560
Stop.__name__ = "Stop"
562
# StopChecker - method
563
StopChecker = dbus.service.method(_interface)(stop_checker)
564
StopChecker.__name__ = "StopChecker"
566
# TimeoutChanged - signal
567
@dbus.service.signal(_interface, signature="t")
568
def TimeoutChanged(self, t):
572
del _datetime_to_dbus_struct
246
return now < (self.last_seen + self.timeout)
576
249
def peer_certificate(session):
577
"Return the peer's OpenPGP certificate as a bytestring"
578
250
# If not an OpenPGP certificate...
579
if (gnutls.library.functions
580
.gnutls_certificate_type_get(session._c_object)
581
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
251
if gnutls.library.functions.gnutls_certificate_type_get\
252
(session._c_object) \
253
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
582
254
# ...do the normal thing
583
255
return session.peer_certificate
584
256
list_size = ctypes.c_uint()
585
cert_list = (gnutls.library.functions
586
.gnutls_certificate_get_peers
587
(session._c_object, ctypes.byref(list_size)))
257
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
258
(session._c_object, ctypes.byref(list_size))
588
259
if list_size.value == 0:
590
261
cert = cert_list[0]
594
265
def fingerprint(openpgp):
595
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
596
# New GnuTLS "datum" with the OpenPGP public key
597
datum = (gnutls.library.types
598
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
601
ctypes.c_uint(len(openpgp))))
602
266
# New empty GnuTLS certificate
603
267
crt = gnutls.library.types.gnutls_openpgp_crt_t()
604
(gnutls.library.functions
605
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
268
gnutls.library.functions.gnutls_openpgp_crt_init\
270
# New GnuTLS "datum" with the OpenPGP public key
271
datum = gnutls.library.types.gnutls_datum_t\
272
(ctypes.cast(ctypes.c_char_p(openpgp),
273
ctypes.POINTER(ctypes.c_ubyte)),
274
ctypes.c_uint(len(openpgp)))
606
275
# Import the OpenPGP public key into the certificate
607
(gnutls.library.functions
608
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
609
gnutls.library.constants
610
.GNUTLS_OPENPGP_FMT_RAW))
611
# Verify the self signature in the key
612
crtverify = ctypes.c_uint()
613
(gnutls.library.functions
614
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
615
if crtverify.value != 0:
616
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
617
raise gnutls.errors.CertificateSecurityError("Verify failed")
276
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
279
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
618
280
# New buffer for the fingerprint
619
buf = ctypes.create_string_buffer(20)
620
buf_len = ctypes.c_size_t()
281
buffer = ctypes.create_string_buffer(20)
282
buffer_length = ctypes.c_size_t()
621
283
# Get the fingerprint from the certificate into the buffer
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
624
ctypes.byref(buf_len)))
284
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
285
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
625
286
# Deinit the certificate
626
287
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
288
# Convert the buffer to a Python bytestring
628
fpr = ctypes.string_at(buf, buf_len.value)
289
fpr = ctypes.string_at(buffer, buffer_length.value)
629
290
# Convert the bytestring to hexadecimal notation
630
291
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
634
class TCP_handler(SocketServer.BaseRequestHandler, object):
295
class tcp_handler(SocketServer.BaseRequestHandler, object):
635
296
"""A TCP request handler class.
636
297
Instantiated by IPv6_TCPServer for each request to handle it.
637
298
Note: This will run in its own forked process."""
639
300
def handle(self):
640
logger.info(u"TCP connection from: %s",
641
unicode(self.client_address))
642
session = (gnutls.connection
643
.ClientSession(self.request,
647
line = self.request.makefile().readline()
648
logger.debug(u"Protocol version: %r", line)
650
if int(line.strip().split()[0]) > 1:
652
except (ValueError, IndexError, RuntimeError), error:
653
logger.error(u"Unknown protocol version: %s", error)
656
# Note: gnutls.connection.X509Credentials is really a generic
657
# GnuTLS certificate credentials object so long as no X.509
658
# keys are added to it. Therefore, we can use it here despite
659
# using OpenPGP certificates.
302
sys.stderr.write(u"TCP request came\n")
303
sys.stderr.write(u"Request: %s\n" % self.request)
304
sys.stderr.write(u"Client Address: %s\n"
305
% unicode(self.client_address))
306
sys.stderr.write(u"Server: %s\n" % self.server)
307
session = gnutls.connection.ClientSession(self.request,
661
311
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
662
312
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
664
# Use a fallback default, since this MUST be set.
665
priority = self.server.settings.get("priority", "NORMAL")
666
(gnutls.library.functions
667
.gnutls_priority_set_direct(session._c_object,
314
priority = "SECURE256"
316
gnutls.library.functions.gnutls_priority_set_direct\
317
(session._c_object, priority, None);
671
320
session.handshake()
672
321
except gnutls.errors.GNUTLSError, error:
673
logger.warning(u"Handshake failed: %s", error)
323
sys.stderr.write(u"Handshake failed: %s\n" % error)
674
324
# Do not run session.bye() here: the session is not
675
325
# established. Just abandon the request.
678
328
fpr = fingerprint(peer_certificate(session))
679
329
except (TypeError, gnutls.errors.GNUTLSError), error:
680
logger.warning(u"Bad certificate: %s", error)
331
sys.stderr.write(u"Bad certificate: %s\n" % error)
683
logger.debug(u"Fingerprint: %s", fpr)
684
for c in self.server.clients:
335
sys.stderr.write(u"Fingerprint: %s\n" % fpr)
685
338
if c.fingerprint == fpr:
689
logger.warning(u"Client not found for fingerprint: %s",
693
341
# Have to check if client.still_valid(), since it is possible
694
342
# that the client timed out while establishing the GnuTLS
696
if not client.still_valid():
697
logger.warning(u"Client %(name)s is invalid",
344
if (not client) or (not client.still_valid()):
347
sys.stderr.write(u"Client %(name)s is invalid\n"
350
sys.stderr.write(u"Client not found for "
351
u"fingerprint: %s\n" % fpr)
701
## This won't work here, since we're in a fork.
702
# client.bump_timeout()
704
355
while sent_size < len(client.secret):
705
356
sent = session.send(client.secret[sent_size:])
706
logger.debug(u"Sent: %d, remaining: %d",
707
sent, len(client.secret)
708
- (sent_size + sent))
358
sys.stderr.write(u"Sent: %d, remaining: %d\n"
359
% (sent, len(client.secret)
360
- (sent_size + sent)))
709
361
sent_size += sent
713
class IPv6_TCPServer(SocketServer.ForkingMixIn,
714
SocketServer.TCPServer, object):
365
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
715
366
"""IPv6 TCP server. Accepts 'None' as address and/or port.
717
settings: Server settings
368
options: Command line options
718
369
clients: Set() of Client objects
719
enabled: Boolean; whether this server is activated yet
721
371
address_family = socket.AF_INET6
722
372
def __init__(self, *args, **kwargs):
723
if "settings" in kwargs:
724
self.settings = kwargs["settings"]
725
del kwargs["settings"]
373
if "options" in kwargs:
374
self.options = kwargs["options"]
375
del kwargs["options"]
726
376
if "clients" in kwargs:
727
377
self.clients = kwargs["clients"]
728
378
del kwargs["clients"]
730
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
379
return super(type(self), self).__init__(*args, **kwargs)
731
380
def server_bind(self):
732
381
"""This overrides the normal server_bind() function
733
382
to bind to an interface if one was specified, and also NOT to
734
383
bind to an address or port if they were not specified."""
735
if self.settings["interface"]:
736
# 25 is from /usr/include/asm-i486/socket.h
737
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
384
if self.options.interface:
385
if not hasattr(socket, "SO_BINDTODEVICE"):
386
# From /usr/include/asm-i486/socket.h
387
socket.SO_BINDTODEVICE = 25
739
389
self.socket.setsockopt(socket.SOL_SOCKET,
741
self.settings["interface"])
390
socket.SO_BINDTODEVICE,
391
self.options.interface)
742
392
except socket.error, error:
743
393
if error[0] == errno.EPERM:
744
logger.error(u"No permission to"
745
u" bind to interface %s",
746
self.settings["interface"])
394
sys.stderr.write(u"Warning: No permission to" \
395
u" bind to interface %s\n"
396
% self.options.interface)
749
399
# Only bind(2) the socket if we really need to.
783
421
datetime.timedelta(1)
784
422
>>> string_to_delta(u'1w')
785
423
datetime.timedelta(7)
786
>>> string_to_delta('5m 30s')
787
datetime.timedelta(0, 330)
789
timevalue = datetime.timedelta(0)
790
for s in interval.split():
792
suffix = unicode(s[-1])
795
delta = datetime.timedelta(value)
797
delta = datetime.timedelta(0, value)
799
delta = datetime.timedelta(0, 0, 0, 0, value)
801
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
803
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
806
except (ValueError, IndexError):
426
suffix=unicode(interval[-1])
427
value=int(interval[:-1])
429
delta = datetime.timedelta(value)
431
delta = datetime.timedelta(0, value)
433
delta = datetime.timedelta(0, 0, 0, 0, value)
435
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
437
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
440
except (ValueError, IndexError):
446
"""From the Avahi server example code"""
447
global group, serviceName, serviceType, servicePort, serviceTXT, \
450
group = dbus.Interface(
451
bus.get_object( avahi.DBUS_NAME,
452
server.EntryGroupNew()),
453
avahi.DBUS_INTERFACE_ENTRY_GROUP)
454
group.connect_to_signal('StateChanged',
455
entry_group_state_changed)
457
sys.stderr.write(u"Adding service '%s' of type '%s' ...\n"
458
% (serviceName, serviceType))
461
serviceInterface, # interface
462
avahi.PROTO_INET6, # protocol
463
dbus.UInt32(0), # flags
464
serviceName, serviceType,
466
dbus.UInt16(servicePort),
467
avahi.string_array_to_txt_array(serviceTXT))
471
def remove_service():
472
"""From the Avahi server example code"""
475
if not group is None:
812
479
def server_state_changed(state):
813
"""Derived from the Avahi example code"""
480
"""From the Avahi server example code"""
814
481
if state == avahi.SERVER_COLLISION:
815
logger.error(u"Zeroconf server name collision")
482
sys.stderr.write(u"WARNING: Server name collision\n")
817
484
elif state == avahi.SERVER_RUNNING:
821
488
def entry_group_state_changed(state, error):
822
"""Derived from the Avahi example code"""
823
logger.debug(u"Avahi state change: %i", state)
489
"""From the Avahi server example code"""
490
global serviceName, server, rename_count
493
sys.stderr.write(u"state change: %i\n" % state)
825
495
if state == avahi.ENTRY_GROUP_ESTABLISHED:
826
logger.debug(u"Zeroconf service established.")
497
sys.stderr.write(u"Service established.\n")
827
498
elif state == avahi.ENTRY_GROUP_COLLISION:
828
logger.warning(u"Zeroconf service name collision.")
500
rename_count = rename_count - 1
502
name = server.GetAlternativeServiceName(name)
503
sys.stderr.write(u"WARNING: Service name collision, "
504
u"changing name to '%s' ...\n" % name)
509
sys.stderr.write(u"ERROR: No suitable service name found "
510
u"after %i retries, exiting.\n"
830
513
elif state == avahi.ENTRY_GROUP_FAILURE:
831
logger.critical(u"Avahi: Error in group state changed %s",
833
raise AvahiGroupError("State changed: %s", str(error))
514
sys.stderr.write(u"Error in group state changed %s\n"
835
520
def if_nametoindex(interface):
836
"""Call the C function if_nametoindex(), or equivalent"""
837
global if_nametoindex
521
"""Call the C function if_nametoindex()"""
839
if_nametoindex = (ctypes.cdll.LoadLibrary
840
(ctypes.util.find_library("c"))
523
libc = ctypes.cdll.LoadLibrary("libc.so.6")
524
return libc.if_nametoindex(interface)
842
525
except (OSError, AttributeError):
843
526
if "struct" not in sys.modules:
845
528
if "fcntl" not in sys.modules:
847
def if_nametoindex(interface):
848
"Get an interface index the hard way, i.e. using fcntl()"
849
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
850
with closing(socket.socket()) as s:
851
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
852
struct.pack("16s16x", interface))
853
interface_index = struct.unpack("I", ifreq[16:20])[0]
854
return interface_index
855
return if_nametoindex(interface)
858
def daemon(nochdir = False, noclose = False):
859
"""See daemon(3). Standard BSD Unix function.
860
This should really exist as os.daemon, but it doesn't (yet)."""
869
# Close all standard open file descriptors
870
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
871
if not stat.S_ISCHR(os.fstat(null).st_mode):
872
raise OSError(errno.ENODEV,
873
"/dev/null not a character device")
874
os.dup2(null, sys.stdin.fileno())
875
os.dup2(null, sys.stdout.fileno())
876
os.dup2(null, sys.stderr.fileno())
882
parser = OptionParser(version = "%%prog %s" % version)
530
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
532
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
533
struct.pack("16s16x", interface))
535
interface_index = struct.unpack("I", ifreq[16:20])[0]
536
return interface_index
539
if __name__ == '__main__':
540
parser = OptionParser()
883
541
parser.add_option("-i", "--interface", type="string",
884
metavar="IF", help="Bind to interface IF")
885
parser.add_option("-a", "--address", type="string",
886
help="Address to listen for requests on")
887
parser.add_option("-p", "--port", type="int",
542
default=None, metavar="IF",
543
help="Bind to interface IF")
544
parser.add_option("--cert", type="string", default="cert.pem",
546
help="Public key certificate PEM file to use")
547
parser.add_option("--key", type="string", default="key.pem",
549
help="Private key PEM file to use")
550
parser.add_option("--ca", type="string", default="ca.pem",
552
help="Certificate Authority certificate PEM file to use")
553
parser.add_option("--crl", type="string", default="crl.pem",
555
help="Certificate Revokation List PEM file to use")
556
parser.add_option("-p", "--port", type="int", default=None,
888
557
help="Port number to receive requests on")
558
parser.add_option("--timeout", type="string", # Parsed later
560
help="Amount of downtime allowed for clients")
561
parser.add_option("--interval", type="string", # Parsed later
563
help="How often to check that a client is up")
889
564
parser.add_option("--check", action="store_true", default=False,
890
565
help="Run self-test")
891
parser.add_option("--debug", action="store_true",
892
help="Debug mode; run in foreground and log to"
894
parser.add_option("--priority", type="string", help="GnuTLS"
895
" priority string (see GnuTLS documentation)")
896
parser.add_option("--servicename", type="string", metavar="NAME",
897
help="Zeroconf service name")
898
parser.add_option("--configdir", type="string",
899
default="/etc/mandos", metavar="DIR",
900
help="Directory to search for configuration"
902
options = parser.parse_args()[0]
566
parser.add_option("--debug", action="store_true", default=False,
568
(options, args) = parser.parse_args()
904
570
if options.check:
906
572
doctest.testmod()
909
# Default values for config file for server-global settings
910
server_defaults = { "interface": "",
915
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
916
"servicename": "Mandos",
919
# Parse config file for server-global settings
920
server_config = ConfigParser.SafeConfigParser(server_defaults)
922
server_config.read(os.path.join(options.configdir, "mandos.conf"))
923
# Convert the SafeConfigParser object to a dict
924
server_settings = server_config.defaults()
925
# Use getboolean on the boolean config option
926
server_settings["debug"] = (server_config.getboolean
927
("DEFAULT", "debug"))
930
# Override the settings from the config file with command line
932
for option in ("interface", "address", "port", "debug",
933
"priority", "servicename", "configdir"):
934
value = getattr(options, option)
935
if value is not None:
936
server_settings[option] = value
938
# Now we have our good server settings in "server_settings"
940
debug = server_settings["debug"]
943
syslogger.setLevel(logging.WARNING)
944
console.setLevel(logging.WARNING)
946
if server_settings["servicename"] != "Mandos":
947
syslogger.setFormatter(logging.Formatter
948
('Mandos (%s): %%(levelname)s:'
950
% server_settings["servicename"]))
952
# Parse config file with clients
953
client_defaults = { "timeout": "1h",
955
"checker": "fping -q -- %(host)s",
958
client_config = ConfigParser.SafeConfigParser(client_defaults)
959
client_config.read(os.path.join(server_settings["configdir"],
963
tcp_server = IPv6_TCPServer((server_settings["address"],
964
server_settings["port"]),
966
settings=server_settings,
968
pidfilename = "/var/run/mandos.pid"
970
pidfile = open(pidfilename, "w")
971
except IOError, error:
972
logger.error("Could not open file %r", pidfilename)
977
uid = pwd.getpwnam("mandos").pw_uid
980
uid = pwd.getpwnam("nobody").pw_uid
984
gid = pwd.getpwnam("mandos").pw_gid
987
gid = pwd.getpwnam("nogroup").pw_gid
993
except OSError, error:
994
if error[0] != errno.EPERM:
998
service = AvahiService(name = server_settings["servicename"],
999
servicetype = "_mandos._tcp", )
1000
if server_settings["interface"]:
1001
service.interface = (if_nametoindex
1002
(server_settings["interface"]))
1007
# From the Avahi example code
575
# Parse the time arguments
577
options.timeout = string_to_delta(options.timeout)
579
parser.error("option --timeout: Unparseable time")
581
options.interval = string_to_delta(options.interval)
583
parser.error("option --interval: Unparseable time")
586
defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
587
client_config = ConfigParser.SafeConfigParser(defaults)
588
#client_config.readfp(open("secrets.conf"), "secrets.conf")
589
client_config.read("mandos-clients.conf")
591
# From the Avahi server example code
1008
592
DBusGMainLoop(set_as_default=True )
1009
593
main_loop = gobject.MainLoop()
1010
594
bus = dbus.SystemBus()
1011
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1012
avahi.DBUS_PATH_SERVER),
1013
avahi.DBUS_INTERFACE_SERVER)
595
server = dbus.Interface(
596
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
597
avahi.DBUS_INTERFACE_SERVER )
1014
598
# End of Avahi example code
1015
bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
600
debug = options.debug
1017
603
def remove_from_clients(client):
1018
604
clients.remove(client)
1020
logger.critical(u"No clients left, exiting")
607
sys.stderr.write(u"No clients left, exiting\n")
1023
clients.update(Set(Client(name = section,
610
clients.update(Set(Client(name=section, options=options,
1024
611
stop_hook = remove_from_clients,
1026
= dict(client_config.items(section)))
612
**(dict(client_config\
1027
614
for section in client_config.sections()))
1029
logger.critical(u"No clients defined")
1033
# Redirect stdin so all checkers get /dev/null
1034
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1035
os.dup2(null, sys.stdin.fileno())
1039
# No console logging
1040
logger.removeHandler(console)
1041
# Close all input and output, do double fork, etc.
1046
pidfile.write(str(pid) + "\n")
1050
logger.error(u"Could not write to file %r with PID %d",
1053
# "pidfile" was never created
1058
"Cleanup function; run on exit"
1060
# From the Avahi example code
1061
if not group is None:
1064
# End of Avahi example code
1067
client = clients.pop()
1068
client.stop_hook = None
1071
atexit.register(cleanup)
1074
signal.signal(signal.SIGINT, signal.SIG_IGN)
1075
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1076
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1078
615
for client in clients:
1082
tcp_server.server_activate()
1084
# Find out what port we got
1085
service.port = tcp_server.socket.getsockname()[1]
1086
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
1087
u" scope_id %d" % tcp_server.socket.getsockname())
1089
#service.interface = tcp_server.socket.getsockname()[3]
618
tcp_server = IPv6_TCPServer((None, options.port),
622
# Find out what random port we got
623
servicePort = tcp_server.socket.getsockname()[1]
625
sys.stderr.write(u"Now listening on port %d\n" % servicePort)
627
if options.interface is not None:
628
serviceInterface = if_nametoindex(options.interface)
630
# From the Avahi server example code
631
server.connect_to_signal("StateChanged", server_state_changed)
632
server_state_changed(server.GetState())
633
# End of Avahi example code
635
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
636
lambda *args, **kwargs:
637
tcp_server.handle_request(*args[2:],
1092
# From the Avahi example code
1093
server.connect_to_signal("StateChanged", server_state_changed)
1095
server_state_changed(server.GetState())
1096
except dbus.exceptions.DBusException, error:
1097
logger.critical(u"DBusException: %s", error)
1099
# End of Avahi example code
1101
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1102
lambda *args, **kwargs:
1103
(tcp_server.handle_request
1104
(*args[2:], **kwargs) or True))
1106
logger.debug(u"Starting main loop")
1108
except AvahiError, error:
1109
logger.critical(u"AvahiError: %s" + unicode(error))
1111
641
except KeyboardInterrupt:
1115
if __name__ == '__main__':
646
# From the Avahi server example code
647
if not group is None:
649
# End of Avahi example code
651
for client in clients:
652
client.stop_hook = None