188
170
# End of Avahi example code
191
def _datetime_to_dbus(dt, variant_level=0):
192
"""Convert a UTC datetime.datetime() to a D-Bus type."""
193
return dbus.String(dt.isoformat(), variant_level=variant_level)
196
class Client(object):
173
class Client(dbus.service.Object):
197
174
"""A representation of a client host served by this server.
200
name: string; from the config file, used in log messages and
176
name: string; from the config file, used in log messages
202
177
fingerprint: string (40 or 32 hexadecimal digits); used to
203
178
uniquely identify the client
204
secret: bytestring; sent verbatim (over TLS) to client
205
host: string; available for use by the checker command
206
created: datetime.datetime(); (UTC) object creation
207
last_enabled: datetime.datetime(); (UTC)
179
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
209
183
last_checked_ok: datetime.datetime(); (UTC) or None
210
timeout: datetime.timedelta(); How long from last_checked_ok
211
until this client is invalid
212
interval: datetime.timedelta(); How often to start a new checker
213
disable_hook: If set, called by disable() as disable_hook(self)
214
checker: subprocess.Popen(); a running checker process used
215
to see if the client lives.
216
'None' if no process is running.
184
timeout: datetime.timedelta(); How long from last_checked_ok
185
until this client is invalid
186
interval: datetime.timedelta(); How often to start a new checker
187
stop_hook: If set, called by stop() as stop_hook(self)
188
checker: subprocess.Popen(); a running checker process used
189
to see if the client lives.
190
'None' if no process is running.
217
191
checker_initiator_tag: a gobject event source tag, or None
218
disable_initiator_tag: - '' -
192
stop_initiator_tag: - '' -
219
193
checker_callback_tag: - '' -
220
194
checker_command: string; External command which is run to check if
221
195
client lives. %() expansions are done at
222
196
runtime with vars(self) as dict, so that for
223
197
instance %(name)s can be used in the command.
224
current_checker_command: string; current running checker_command
199
_timeout: Real variable for 'timeout'
200
_interval: Real variable for 'interval'
201
_timeout_milliseconds: Used when calling gobject.timeout_add()
202
_interval_milliseconds: - '' -
228
def _datetime_to_milliseconds(dt):
229
"Convert a datetime.datetime() to milliseconds"
230
return ((dt.days * 24 * 60 * 60 * 1000)
231
+ (dt.seconds * 1000)
232
+ (dt.microseconds // 1000))
234
def timeout_milliseconds(self):
235
"Return the 'timeout' attribute in milliseconds"
236
return self._datetime_to_milliseconds(self.timeout)
238
def interval_milliseconds(self):
239
"Return the 'interval' attribute in milliseconds"
240
return self._datetime_to_milliseconds(self.interval)
242
def __init__(self, name = None, disable_hook=None, config=None):
204
def _set_timeout(self, timeout):
205
"Setter function for the 'timeout' attribute"
206
self._timeout = timeout
207
self._timeout_milliseconds = ((self.timeout.days
208
* 24 * 60 * 60 * 1000)
209
+ (self.timeout.seconds * 1000)
210
+ (self.timeout.microseconds
213
self.TimeoutChanged(self._timeout_milliseconds)
214
timeout = property(lambda self: self._timeout, _set_timeout)
217
def _set_interval(self, interval):
218
"Setter function for the 'interval' attribute"
219
self._interval = interval
220
self._interval_milliseconds = ((self.interval.days
221
* 24 * 60 * 60 * 1000)
222
+ (self.interval.seconds
224
+ (self.interval.microseconds
227
self.IntervalChanged(self._interval_milliseconds)
228
interval = property(lambda self: self._interval, _set_interval)
231
def __init__(self, name = None, stop_hook=None, config=None):
243
232
"""Note: the 'checker' key in 'config' sets the
244
233
'checker_command' attribute and *not* the 'checker'
235
dbus.service.Object.__init__(self, bus,
237
% name.replace(".", "_"))
247
238
if config is None:
249
241
logger.debug(u"Creating client %r", self.name)
250
242
# Uppercase and remove spaces from fingerprint for later
251
243
# comparison purposes with return value from the fingerprint()
253
self.fingerprint = (config[u"fingerprint"].upper()
245
self.fingerprint = (config["fingerprint"].upper()
254
246
.replace(u" ", u""))
255
247
logger.debug(u" Fingerprint: %s", self.fingerprint)
256
if u"secret" in config:
257
self.secret = config[u"secret"].decode(u"base64")
258
elif u"secfile" in config:
248
if "secret" in config:
249
self.secret = config["secret"].decode(u"base64")
250
elif "secfile" in config:
259
251
with closing(open(os.path.expanduser
260
252
(os.path.expandvars
261
(config[u"secfile"])))) as secfile:
253
(config["secfile"])))) as secfile:
262
254
self.secret = secfile.read()
264
256
raise TypeError(u"No secret or secfile for client %s"
266
self.host = config.get(u"host", u"")
258
self.host = config.get("host", "")
267
259
self.created = datetime.datetime.utcnow()
269
self.last_enabled = None
270
261
self.last_checked_ok = None
271
self.timeout = string_to_delta(config[u"timeout"])
272
self.interval = string_to_delta(config[u"interval"])
273
self.disable_hook = disable_hook
262
self.timeout = string_to_delta(config["timeout"])
263
self.interval = string_to_delta(config["interval"])
264
self.stop_hook = stop_hook
274
265
self.checker = None
275
266
self.checker_initiator_tag = None
276
self.disable_initiator_tag = None
267
self.stop_initiator_tag = None
277
268
self.checker_callback_tag = None
278
self.checker_command = config[u"checker"]
279
self.current_checker_command = None
280
self.last_connect = None
269
self.check_command = config["checker"]
283
272
"""Start this client's checker and timeout hooks"""
284
self.last_enabled = datetime.datetime.utcnow()
273
self.started = datetime.datetime.utcnow()
285
274
# Schedule a new checker to be started an 'interval' from now,
286
275
# and every interval from then on.
287
276
self.checker_initiator_tag = (gobject.timeout_add
288
(self.interval_milliseconds(),
277
(self._interval_milliseconds,
289
278
self.start_checker))
290
279
# Also start a new checker *right now*.
291
280
self.start_checker()
292
# Schedule a disable() when 'timeout' has passed
293
self.disable_initiator_tag = (gobject.timeout_add
294
(self.timeout_milliseconds(),
281
# Schedule a stop() when 'timeout' has passed
282
self.stop_initiator_tag = (gobject.timeout_add
283
(self._timeout_milliseconds,
286
self.StateChanged(True)
299
"""Disable this client."""
300
if not getattr(self, "enabled", False):
289
"""Stop this client."""
290
if getattr(self, "started", None) is not None:
291
logger.info(u"Stopping client %s", self.name)
302
logger.info(u"Disabling client %s", self.name)
303
if getattr(self, u"disable_initiator_tag", False):
304
gobject.source_remove(self.disable_initiator_tag)
305
self.disable_initiator_tag = None
306
if getattr(self, u"checker_initiator_tag", False):
294
if getattr(self, "stop_initiator_tag", False):
295
gobject.source_remove(self.stop_initiator_tag)
296
self.stop_initiator_tag = None
297
if getattr(self, "checker_initiator_tag", False):
307
298
gobject.source_remove(self.checker_initiator_tag)
308
299
self.checker_initiator_tag = None
309
300
self.stop_checker()
310
if self.disable_hook:
311
self.disable_hook(self)
305
self.StateChanged(False)
313
306
# Do not run this again if called by a gobject.timeout_add
316
309
def __del__(self):
317
self.disable_hook = None
310
self.stop_hook = None
320
def checker_callback(self, pid, condition, command):
313
def checker_callback(self, pid, condition):
321
314
"""The checker has completed, so take appropriate actions."""
322
315
self.checker_callback_tag = None
323
316
self.checker = None
324
if os.WIFEXITED(condition):
325
exitstatus = os.WEXITSTATUS(condition)
327
logger.info(u"Checker for %(name)s succeeded",
331
logger.info(u"Checker for %(name)s failed",
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):
334
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)
337
def checked_ok(self):
335
def bump_timeout(self):
338
336
"""Bump up the timeout for this client.
340
337
This should only be called when the client has been seen,
343
340
self.last_checked_ok = datetime.datetime.utcnow()
344
gobject.source_remove(self.disable_initiator_tag)
345
self.disable_initiator_tag = (gobject.timeout_add
346
(self.timeout_milliseconds(),
341
gobject.source_remove(self.stop_initiator_tag)
342
self.stop_initiator_tag = (gobject.timeout_add
343
(self._timeout_milliseconds,
349
346
def start_checker(self):
350
347
"""Start a new checker subprocess if one is not running.
352
348
If a checker already exists, leave it running and do
354
350
# The reason for not killing a running checker is that if we
435
412
def still_valid(self):
436
413
"""Has the timeout not yet passed for this client?"""
437
if not getattr(self, u"enabled", False):
439
416
now = datetime.datetime.utcnow()
440
417
if self.last_checked_ok is None:
441
418
return now < (self.created + self.timeout)
443
420
return now < (self.last_checked_ok + self.timeout)
446
class ClientDBus(Client, dbus.service.Object):
447
"""A Client class using D-Bus
450
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
452
# dbus.service.Object doesn't use super(), so we can't either.
454
def __init__(self, *args, **kwargs):
455
Client.__init__(self, *args, **kwargs)
456
# Only now, when this client is initialized, can it show up on
458
self.dbus_object_path = (dbus.ObjectPath
460
+ self.name.replace(u".", u"_")))
461
dbus.service.Object.__init__(self, bus,
462
self.dbus_object_path)
464
oldstate = getattr(self, u"enabled", False)
465
r = Client.enable(self)
466
if oldstate != self.enabled:
468
self.PropertyChanged(dbus.String(u"enabled"),
469
dbus.Boolean(True, variant_level=1))
470
self.PropertyChanged(dbus.String(u"last_enabled"),
471
(_datetime_to_dbus(self.last_enabled,
475
def disable(self, signal = True):
476
oldstate = getattr(self, u"enabled", False)
477
r = Client.disable(self)
478
if signal and oldstate != self.enabled:
480
self.PropertyChanged(dbus.String(u"enabled"),
481
dbus.Boolean(False, variant_level=1))
484
def __del__(self, *args, **kwargs):
486
self.remove_from_connection()
489
if hasattr(dbus.service.Object, u"__del__"):
490
dbus.service.Object.__del__(self, *args, **kwargs)
491
Client.__del__(self, *args, **kwargs)
493
def checker_callback(self, pid, condition, command,
495
self.checker_callback_tag = None
498
self.PropertyChanged(dbus.String(u"checker_running"),
499
dbus.Boolean(False, variant_level=1))
500
if os.WIFEXITED(condition):
501
exitstatus = os.WEXITSTATUS(condition)
503
self.CheckerCompleted(dbus.Int16(exitstatus),
504
dbus.Int64(condition),
505
dbus.String(command))
508
self.CheckerCompleted(dbus.Int16(-1),
509
dbus.Int64(condition),
510
dbus.String(command))
512
return Client.checker_callback(self, pid, condition, command,
515
def checked_ok(self, *args, **kwargs):
516
r = Client.checked_ok(self, *args, **kwargs)
518
self.PropertyChanged(
519
dbus.String(u"last_checked_ok"),
520
(_datetime_to_dbus(self.last_checked_ok,
524
def start_checker(self, *args, **kwargs):
525
old_checker = self.checker
526
if self.checker is not None:
527
old_checker_pid = self.checker.pid
529
old_checker_pid = None
530
r = Client.start_checker(self, *args, **kwargs)
531
# Only if new checker process was started
532
if (self.checker is not None
533
and old_checker_pid != self.checker.pid):
535
self.CheckerStarted(self.current_checker_command)
536
self.PropertyChanged(
537
dbus.String(u"checker_running"),
538
dbus.Boolean(True, variant_level=1))
541
def stop_checker(self, *args, **kwargs):
542
old_checker = getattr(self, u"checker", None)
543
r = Client.stop_checker(self, *args, **kwargs)
544
if (old_checker is not None
545
and getattr(self, u"checker", None) is None):
546
self.PropertyChanged(dbus.String(u"checker_running"),
547
dbus.Boolean(False, variant_level=1))
550
422
## D-Bus methods & signals
551
_interface = u"se.bsnet.fukt.Mandos.Client"
554
@dbus.service.method(_interface)
556
return self.checked_ok()
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):
558
440
# CheckerCompleted - signal
559
@dbus.service.signal(_interface, signature=u"nxs")
560
def CheckerCompleted(self, exitcode, waitstatus, command):
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
564
452
# CheckerStarted - signal
565
@dbus.service.signal(_interface, signature=u"s")
453
@dbus.service.signal(_interface, signature="s")
566
454
def CheckerStarted(self, command):
570
# GetAllProperties - method
571
@dbus.service.method(_interface, out_signature=u"a{sv}")
572
def GetAllProperties(self):
574
return dbus.Dictionary({
575
dbus.String(u"name"):
576
dbus.String(self.name, variant_level=1),
577
dbus.String(u"fingerprint"):
578
dbus.String(self.fingerprint, variant_level=1),
579
dbus.String(u"host"):
580
dbus.String(self.host, variant_level=1),
581
dbus.String(u"created"):
582
_datetime_to_dbus(self.created, variant_level=1),
583
dbus.String(u"last_enabled"):
584
(_datetime_to_dbus(self.last_enabled,
586
if self.last_enabled is not None
587
else dbus.Boolean(False, variant_level=1)),
588
dbus.String(u"enabled"):
589
dbus.Boolean(self.enabled, variant_level=1),
590
dbus.String(u"last_checked_ok"):
591
(_datetime_to_dbus(self.last_checked_ok,
593
if self.last_checked_ok is not None
594
else dbus.Boolean (False, variant_level=1)),
595
dbus.String(u"timeout"):
596
dbus.UInt64(self.timeout_milliseconds(),
598
dbus.String(u"interval"):
599
dbus.UInt64(self.interval_milliseconds(),
601
dbus.String(u"checker"):
602
dbus.String(self.checker_command,
604
dbus.String(u"checker_running"):
605
dbus.Boolean(self.checker is not None,
607
dbus.String(u"object_path"):
608
dbus.ObjectPath(self.dbus_object_path,
612
# IsStillValid - method
613
@dbus.service.method(_interface, out_signature=u"b")
614
def IsStillValid(self):
615
return self.still_valid()
617
# PropertyChanged - signal
618
@dbus.service.signal(_interface, signature=u"sv")
619
def PropertyChanged(self, property, value):
623
# ReceivedSecret - signal
624
@dbus.service.signal(_interface)
625
def ReceivedSecret(self):
630
@dbus.service.signal(_interface)
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
635
510
# SetChecker - method
636
@dbus.service.method(_interface, in_signature=u"s")
511
@dbus.service.method(_interface, in_signature="s")
637
512
def SetChecker(self, checker):
638
513
"D-Bus setter method"
639
514
self.checker_command = checker
641
self.PropertyChanged(dbus.String(u"checker"),
642
dbus.String(self.checker_command,
645
516
# SetHost - method
646
@dbus.service.method(_interface, in_signature=u"s")
517
@dbus.service.method(_interface, in_signature="s")
647
518
def SetHost(self, host):
648
519
"D-Bus setter method"
651
self.PropertyChanged(dbus.String(u"host"),
652
dbus.String(self.host, variant_level=1))
654
522
# SetInterval - method
655
@dbus.service.method(_interface, in_signature=u"t")
523
@dbus.service.method(_interface, in_signature="t")
656
524
def SetInterval(self, milliseconds):
657
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
659
self.PropertyChanged(dbus.String(u"interval"),
660
(dbus.UInt64(self.interval_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)
663
532
# SetSecret - method
664
@dbus.service.method(_interface, in_signature=u"ay",
533
@dbus.service.method(_interface, in_signature="ay",
665
534
byte_arrays=True)
666
535
def SetSecret(self, secret):
667
536
"D-Bus setter method"
668
537
self.secret = str(secret)
670
# SetTimeout - method
671
@dbus.service.method(_interface, in_signature=u"t")
672
def SetTimeout(self, milliseconds):
673
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
675
self.PropertyChanged(dbus.String(u"timeout"),
676
(dbus.UInt64(self.timeout_milliseconds(),
680
@dbus.service.method(_interface)
540
Start = dbus.service.method(_interface)(start)
541
Start.__name__ = "Start"
685
543
# StartChecker - method
686
@dbus.service.method(_interface)
687
def StartChecker(self):
692
@dbus.service.method(_interface)
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"
697
562
# StopChecker - method
698
@dbus.service.method(_interface)
699
def StopChecker(self):
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
705
class ClientHandler(SocketServer.BaseRequestHandler, object):
706
"""A class to handle client connections.
708
Instantiated once for each connection to handle it.
576
def peer_certificate(session):
577
"Return the peer's OpenPGP certificate as a bytestring"
578
# 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):
582
# ...do the normal thing
583
return session.peer_certificate
584
list_size = ctypes.c_uint()
585
cert_list = (gnutls.library.functions
586
.gnutls_certificate_get_peers
587
(session._c_object, ctypes.byref(list_size)))
588
if list_size.value == 0:
591
return ctypes.string_at(cert.data, cert.size)
594
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
# New empty GnuTLS certificate
603
crt = gnutls.library.types.gnutls_openpgp_crt_t()
604
(gnutls.library.functions
605
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
606
# 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")
618
# New buffer for the fingerprint
619
buf = ctypes.create_string_buffer(20)
620
buf_len = ctypes.c_size_t()
621
# 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)))
625
# Deinit the certificate
626
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
# Convert the buffer to a Python bytestring
628
fpr = ctypes.string_at(buf, buf_len.value)
629
# Convert the bytestring to hexadecimal notation
630
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
634
class TCP_handler(SocketServer.BaseRequestHandler, object):
635
"""A TCP request handler class.
636
Instantiated by IPv6_TCPServer for each request to handle it.
709
637
Note: This will run in its own forked process."""
711
639
def handle(self):
712
640
logger.info(u"TCP connection from: %s",
713
641
unicode(self.client_address))
714
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
715
# Open IPC pipe to parent process
716
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
717
session = (gnutls.connection
718
.ClientSession(self.request,
722
line = self.request.makefile().readline()
723
logger.debug(u"Protocol version: %r", line)
725
if int(line.strip().split()[0]) > 1:
727
except (ValueError, IndexError, RuntimeError), error:
728
logger.error(u"Unknown protocol version: %s", error)
731
# Note: gnutls.connection.X509Credentials is really a
732
# generic GnuTLS certificate credentials object so long as
733
# no X.509 keys are added to it. Therefore, we can use it
734
# here despite using OpenPGP certificates.
736
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
737
# u"+AES-256-CBC", u"+SHA1",
738
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
740
# Use a fallback default, since this MUST be set.
741
priority = self.server.gnutls_priority
744
(gnutls.library.functions
745
.gnutls_priority_set_direct(session._c_object,
750
except gnutls.errors.GNUTLSError, error:
751
logger.warning(u"Handshake failed: %s", error)
752
# Do not run session.bye() here: the session is not
753
# established. Just abandon the request.
755
logger.debug(u"Handshake succeeded")
757
fpr = self.fingerprint(self.peer_certificate(session))
758
except (TypeError, gnutls.errors.GNUTLSError), error:
759
logger.warning(u"Bad certificate: %s", error)
762
logger.debug(u"Fingerprint: %s", fpr)
764
for c in self.server.clients:
765
if c.fingerprint == fpr:
769
ipc.write(u"NOTFOUND %s\n" % fpr)
772
# Have to check if client.still_valid(), since it is
773
# possible that the client timed out while establishing
774
# the GnuTLS session.
775
if not client.still_valid():
776
ipc.write(u"INVALID %s\n" % client.name)
779
ipc.write(u"SENDING %s\n" % client.name)
781
while sent_size < len(client.secret):
782
sent = session.send(client.secret[sent_size:])
783
logger.debug(u"Sent: %d, remaining: %d",
784
sent, len(client.secret)
785
- (sent_size + sent))
790
def peer_certificate(session):
791
"Return the peer's OpenPGP certificate as a bytestring"
792
# If not an OpenPGP certificate...
793
if (gnutls.library.functions
794
.gnutls_certificate_type_get(session._c_object)
795
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
796
# ...do the normal thing
797
return session.peer_certificate
798
list_size = ctypes.c_uint(1)
799
cert_list = (gnutls.library.functions
800
.gnutls_certificate_get_peers
801
(session._c_object, ctypes.byref(list_size)))
802
if not bool(cert_list) and list_size.value != 0:
803
raise gnutls.errors.GNUTLSError(u"error getting peer"
805
if list_size.value == 0:
808
return ctypes.string_at(cert.data, cert.size)
811
def fingerprint(openpgp):
812
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
813
# New GnuTLS "datum" with the OpenPGP public key
814
datum = (gnutls.library.types
815
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
818
ctypes.c_uint(len(openpgp))))
819
# New empty GnuTLS certificate
820
crt = gnutls.library.types.gnutls_openpgp_crt_t()
821
(gnutls.library.functions
822
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
823
# Import the OpenPGP public key into the certificate
824
(gnutls.library.functions
825
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
826
gnutls.library.constants
827
.GNUTLS_OPENPGP_FMT_RAW))
828
# Verify the self signature in the key
829
crtverify = ctypes.c_uint()
830
(gnutls.library.functions
831
.gnutls_openpgp_crt_verify_self(crt, 0,
832
ctypes.byref(crtverify)))
833
if crtverify.value != 0:
834
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
835
raise (gnutls.errors.CertificateSecurityError
837
# New buffer for the fingerprint
838
buf = ctypes.create_string_buffer(20)
839
buf_len = ctypes.c_size_t()
840
# Get the fingerprint from the certificate into the buffer
841
(gnutls.library.functions
842
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
843
ctypes.byref(buf_len)))
844
# Deinit the certificate
845
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
846
# Convert the buffer to a Python bytestring
847
fpr = ctypes.string_at(buf, buf_len.value)
848
# Convert the bytestring to hexadecimal notation
849
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
853
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
854
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
856
Assumes a gobject.MainLoop event loop.
858
def process_request(self, request, client_address):
859
"""Overrides and wraps the original process_request().
861
This function creates a new pipe in self.pipe
863
self.pipe = os.pipe()
864
super(ForkingMixInWithPipe,
865
self).process_request(request, client_address)
866
os.close(self.pipe[1]) # close write end
867
# Call "handle_ipc" for both data and EOF events
868
gobject.io_add_watch(self.pipe[0],
869
gobject.IO_IN | gobject.IO_HUP,
871
def handle_ipc(source, condition):
872
"""Dummy function; override as necessary"""
877
class IPv6_TCPServer(ForkingMixInWithPipe,
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.
661
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
662
# "+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,
672
except gnutls.errors.GNUTLSError, error:
673
logger.warning(u"Handshake failed: %s", error)
674
# Do not run session.bye() here: the session is not
675
# established. Just abandon the request.
678
fpr = fingerprint(peer_certificate(session))
679
except (TypeError, gnutls.errors.GNUTLSError), error:
680
logger.warning(u"Bad certificate: %s", error)
683
logger.debug(u"Fingerprint: %s", fpr)
684
for c in self.server.clients:
685
if c.fingerprint == fpr:
689
logger.warning(u"Client not found for fingerprint: %s",
693
# Have to check if client.still_valid(), since it is possible
694
# that the client timed out while establishing the GnuTLS
696
if not client.still_valid():
697
logger.warning(u"Client %(name)s is invalid",
701
## This won't work here, since we're in a fork.
702
# client.bump_timeout()
704
while sent_size < len(client.secret):
705
sent = session.send(client.secret[sent_size:])
706
logger.debug(u"Sent: %d, remaining: %d",
707
sent, len(client.secret)
708
- (sent_size + sent))
713
class IPv6_TCPServer(SocketServer.ForkingMixIn,
878
714
SocketServer.TCPServer, object):
879
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
715
"""IPv6 TCP server. Accepts 'None' as address and/or port.
717
settings: Server settings
718
clients: Set() of Client objects
882
719
enabled: Boolean; whether this server is activated yet
883
interface: None or a network interface name (string)
884
use_ipv6: Boolean; to use IPv6 or not
886
clients: Set() of Client objects
887
gnutls_priority GnuTLS priority string
888
use_dbus: Boolean; to emit D-Bus signals or not
890
def __init__(self, server_address, RequestHandlerClass,
891
interface=None, use_ipv6=True, clients=None,
892
gnutls_priority=None, use_dbus=True):
721
address_family = socket.AF_INET6
722
def __init__(self, *args, **kwargs):
723
if "settings" in kwargs:
724
self.settings = kwargs["settings"]
725
del kwargs["settings"]
726
if "clients" in kwargs:
727
self.clients = kwargs["clients"]
728
del kwargs["clients"]
893
729
self.enabled = False
894
self.interface = interface
896
self.address_family = socket.AF_INET6
897
self.clients = clients
898
self.use_dbus = use_dbus
899
self.gnutls_priority = gnutls_priority
900
SocketServer.TCPServer.__init__(self, server_address,
730
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
902
731
def server_bind(self):
903
732
"""This overrides the normal server_bind() function
904
733
to bind to an interface if one was specified, and also NOT to
905
734
bind to an address or port if they were not specified."""
906
if self.interface is not None:
735
if self.settings["interface"]:
736
# 25 is from /usr/include/asm-i486/socket.h
737
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
908
739
self.socket.setsockopt(socket.SOL_SOCKET,
910
str(self.interface + u'\0'))
741
self.settings["interface"])
911
742
except socket.error, error:
912
743
if error[0] == errno.EPERM:
913
744
logger.error(u"No permission to"
914
745
u" bind to interface %s",
746
self.settings["interface"])
918
749
# Only bind(2) the socket if we really need to.
919
750
if self.server_address[0] or self.server_address[1]:
920
751
if not self.server_address[0]:
921
if self.address_family == socket.AF_INET6:
922
any_address = u"::" # in6addr_any
924
any_address = socket.INADDR_ANY
925
self.server_address = (any_address,
753
self.server_address = (in6addr_any,
926
754
self.server_address[1])
927
755
elif not self.server_address[1]:
928
756
self.server_address = (self.server_address[0],
758
# if self.settings["interface"]:
931
759
# self.server_address = (self.server_address[0],
936
return SocketServer.TCPServer.server_bind(self)
765
return super(IPv6_TCPServer, self).server_bind()
937
766
def server_activate(self):
939
return SocketServer.TCPServer.server_activate(self)
768
return super(IPv6_TCPServer, self).server_activate()
940
769
def enable(self):
941
770
self.enabled = True
942
def handle_ipc(self, source, condition, file_objects={}):
944
gobject.IO_IN: u"IN", # There is data to read.
945
gobject.IO_OUT: u"OUT", # Data can be written (without
947
gobject.IO_PRI: u"PRI", # There is urgent data to read.
948
gobject.IO_ERR: u"ERR", # Error condition.
949
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
950
# broken, usually for pipes and
953
conditions_string = ' | '.join(name
955
condition_names.iteritems()
957
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
960
# Turn the pipe file descriptor into a Python file object
961
if source not in file_objects:
962
file_objects[source] = os.fdopen(source, u"r", 1)
964
# Read a line from the file object
965
cmdline = file_objects[source].readline()
966
if not cmdline: # Empty line means end of file
968
file_objects[source].close()
969
del file_objects[source]
971
# Stop calling this function
974
logger.debug(u"IPC command: %r", cmdline)
976
# Parse and act on command
977
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
979
if cmd == u"NOTFOUND":
980
logger.warning(u"Client not found for fingerprint: %s",
984
mandos_dbus_service.ClientNotFound(args)
985
elif cmd == u"INVALID":
986
for client in self.clients:
987
if client.name == args:
988
logger.warning(u"Client %s is invalid", args)
994
logger.error(u"Unknown client %s is invalid", args)
995
elif cmd == u"SENDING":
996
for client in self.clients:
997
if client.name == args:
998
logger.info(u"Sending secret to %s", client.name)
1002
client.ReceivedSecret()
1005
logger.error(u"Sending secret to unknown client %s",
1008
logger.error(u"Unknown IPC command: %r", cmdline)
1010
# Keep calling this function
1014
773
def string_to_delta(interval):
1015
774
"""Parse a string and return a datetime.timedelta
1017
>>> string_to_delta(u'7d')
776
>>> string_to_delta('7d')
1018
777
datetime.timedelta(7)
1019
>>> string_to_delta(u'60s')
778
>>> string_to_delta('60s')
1020
779
datetime.timedelta(0, 60)
1021
>>> string_to_delta(u'60m')
780
>>> string_to_delta('60m')
1022
781
datetime.timedelta(0, 3600)
1023
>>> string_to_delta(u'24h')
782
>>> string_to_delta('24h')
1024
783
datetime.timedelta(1)
1025
784
>>> string_to_delta(u'1w')
1026
785
datetime.timedelta(7)
1027
>>> string_to_delta(u'5m 30s')
786
>>> string_to_delta('5m 30s')
1028
787
datetime.timedelta(0, 330)
1030
789
timevalue = datetime.timedelta(0)
1161
909
# Default values for config file for server-global settings
1162
server_defaults = { u"interface": u"",
1167
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1168
u"servicename": u"Mandos",
1169
u"use_dbus": u"True",
1170
u"use_ipv6": u"True",
910
server_defaults = { "interface": "",
915
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
916
"servicename": "Mandos",
1173
919
# Parse config file for server-global settings
1174
920
server_config = ConfigParser.SafeConfigParser(server_defaults)
1175
921
del server_defaults
1176
server_config.read(os.path.join(options.configdir,
922
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1178
923
# Convert the SafeConfigParser object to a dict
1179
924
server_settings = server_config.defaults()
1180
# Use the appropriate methods on the non-string config options
1181
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1182
server_settings[option] = server_config.getboolean(u"DEFAULT",
1184
if server_settings["port"]:
1185
server_settings["port"] = server_config.getint(u"DEFAULT",
925
# Use getboolean on the boolean config option
926
server_settings["debug"] = (server_config.getboolean
927
("DEFAULT", "debug"))
1187
928
del server_config
1189
930
# Override the settings from the config file with command line
1190
931
# options, if set.
1191
for option in (u"interface", u"address", u"port", u"debug",
1192
u"priority", u"servicename", u"configdir",
1193
u"use_dbus", u"use_ipv6"):
932
for option in ("interface", "address", "port", "debug",
933
"priority", "servicename", "configdir"):
1194
934
value = getattr(options, option)
1195
935
if value is not None:
1196
936
server_settings[option] = value
1198
# Force all strings to be unicode
1199
for option in server_settings.keys():
1200
if type(server_settings[option]) is str:
1201
server_settings[option] = unicode(server_settings[option])
1202
938
# Now we have our good server settings in "server_settings"
1204
##################################################################
1207
debug = server_settings[u"debug"]
1208
use_dbus = server_settings[u"use_dbus"]
1209
use_ipv6 = server_settings[u"use_ipv6"]
940
debug = server_settings["debug"]
1212
943
syslogger.setLevel(logging.WARNING)
1213
944
console.setLevel(logging.WARNING)
1215
if server_settings[u"servicename"] != u"Mandos":
946
if server_settings["servicename"] != "Mandos":
1216
947
syslogger.setFormatter(logging.Formatter
1217
(u'Mandos (%s) [%%(process)d]:'
1218
u' %%(levelname)s: %%(message)s'
1219
% server_settings[u"servicename"]))
948
('Mandos (%s): %%(levelname)s:'
950
% server_settings["servicename"]))
1221
952
# Parse config file with clients
1222
client_defaults = { u"timeout": u"1h",
1224
u"checker": u"fping -q -- %%(host)s",
953
client_defaults = { "timeout": "1h",
955
"checker": "fping -q -- %(host)s",
1227
958
client_config = ConfigParser.SafeConfigParser(client_defaults)
1228
client_config.read(os.path.join(server_settings[u"configdir"],
1231
global mandos_dbus_service
1232
mandos_dbus_service = None
959
client_config.read(os.path.join(server_settings["configdir"],
1235
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1236
server_settings[u"port"]),
1239
server_settings[u"interface"],
1243
server_settings[u"priority"],
1245
pidfilename = u"/var/run/mandos.pid"
963
tcp_server = IPv6_TCPServer((server_settings["address"],
964
server_settings["port"]),
966
settings=server_settings,
968
pidfilename = "/var/run/mandos.pid"
1247
pidfile = open(pidfilename, u"w")
1249
logger.error(u"Could not open file %r", pidfilename)
970
pidfile = open(pidfilename, "w")
971
except IOError, error:
972
logger.error("Could not open file %r", pidfilename)
1252
uid = pwd.getpwnam(u"_mandos").pw_uid
1253
gid = pwd.getpwnam(u"_mandos").pw_gid
1256
uid = pwd.getpwnam(u"mandos").pw_uid
1257
gid = pwd.getpwnam(u"mandos").pw_gid
1260
uid = pwd.getpwnam(u"nobody").pw_uid
1261
gid = pwd.getpwnam(u"nobody").pw_gid
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
1268
993
except OSError, error:
1269
994
if error[0] != errno.EPERM:
1272
# Enable all possible GnuTLS debugging
1274
# "Use a log level over 10 to enable all debugging options."
1276
gnutls.library.functions.gnutls_global_set_log_level(11)
1278
@gnutls.library.types.gnutls_log_func
1279
def debug_gnutls(level, string):
1280
logger.debug(u"GnuTLS: %s", string[:-1])
1282
(gnutls.library.functions
1283
.gnutls_global_set_log_function(debug_gnutls))
1286
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1287
service = AvahiService(name = server_settings[u"servicename"],
1288
servicetype = u"_mandos._tcp",
1289
protocol = protocol)
998
service = AvahiService(name = server_settings["servicename"],
999
servicetype = "_mandos._tcp", )
1290
1000
if server_settings["interface"]:
1291
1001
service.interface = (if_nametoindex
1292
(str(server_settings[u"interface"])))
1002
(server_settings["interface"]))
1294
1004
global main_loop