160
187
# End of Avahi example code
190
def _datetime_to_dbus(dt, variant_level=0):
191
"""Convert a UTC datetime.datetime() to a D-Bus type."""
192
return dbus.String(dt.isoformat(), variant_level=variant_level)
163
195
class Client(object):
164
196
"""A representation of a client host served by this server.
166
name: string; from the config file, used in log messages
199
name: string; from the config file, used in log messages and
167
201
fingerprint: string (40 or 32 hexadecimal digits); used to
168
202
uniquely identify the client
169
secret: bytestring; sent verbatim (over TLS) to client
170
host: string; available for use by the checker command
171
created: datetime.datetime(); object creation, not client host
172
last_checked_ok: datetime.datetime() or None if not yet checked OK
173
timeout: datetime.timedelta(); How long from last_checked_ok
174
until this client is invalid
175
interval: datetime.timedelta(); How often to start a new checker
176
stop_hook: If set, called by stop() as stop_hook(self)
177
checker: subprocess.Popen(); a running checker process used
178
to see if the client lives.
179
'None' if no process is running.
203
secret: bytestring; sent verbatim (over TLS) to client
204
host: string; available for use by the checker command
205
created: datetime.datetime(); (UTC) object creation
206
last_enabled: datetime.datetime(); (UTC)
208
last_checked_ok: datetime.datetime(); (UTC) or None
209
timeout: datetime.timedelta(); How long from last_checked_ok
210
until this client is invalid
211
interval: datetime.timedelta(); How often to start a new checker
212
disable_hook: If set, called by disable() as disable_hook(self)
213
checker: subprocess.Popen(); a running checker process used
214
to see if the client lives.
215
'None' if no process is running.
180
216
checker_initiator_tag: a gobject event source tag, or None
181
stop_initiator_tag: - '' -
217
disable_initiator_tag: - '' -
182
218
checker_callback_tag: - '' -
183
219
checker_command: string; External command which is run to check if
184
220
client lives. %() expansions are done at
185
221
runtime with vars(self) as dict, so that for
186
222
instance %(name)s can be used in the command.
188
_timeout: Real variable for 'timeout'
189
_interval: Real variable for 'interval'
190
_timeout_milliseconds: Used when calling gobject.timeout_add()
191
_interval_milliseconds: - '' -
223
current_checker_command: string; current running checker_command
193
def _set_timeout(self, timeout):
194
"Setter function for 'timeout' attribute"
195
self._timeout = timeout
196
self._timeout_milliseconds = ((self.timeout.days
197
* 24 * 60 * 60 * 1000)
198
+ (self.timeout.seconds * 1000)
199
+ (self.timeout.microseconds
201
timeout = property(lambda self: self._timeout,
204
def _set_interval(self, interval):
205
"Setter function for 'interval' attribute"
206
self._interval = interval
207
self._interval_milliseconds = ((self.interval.days
208
* 24 * 60 * 60 * 1000)
209
+ (self.interval.seconds
211
+ (self.interval.microseconds
213
interval = property(lambda self: self._interval,
216
def __init__(self, name = None, stop_hook=None, config={}):
227
def _datetime_to_milliseconds(dt):
228
"Convert a datetime.datetime() to milliseconds"
229
return ((dt.days * 24 * 60 * 60 * 1000)
230
+ (dt.seconds * 1000)
231
+ (dt.microseconds // 1000))
233
def timeout_milliseconds(self):
234
"Return the 'timeout' attribute in milliseconds"
235
return self._datetime_to_milliseconds(self.timeout)
237
def interval_milliseconds(self):
238
"Return the 'interval' attribute in milliseconds"
239
return self._datetime_to_milliseconds(self.interval)
241
def __init__(self, name = None, disable_hook=None, config=None):
217
242
"""Note: the 'checker' key in 'config' sets the
218
243
'checker_command' attribute and *not* the 'checker'
221
248
logger.debug(u"Creating client %r", self.name)
222
249
# Uppercase and remove spaces from fingerprint for later
223
250
# comparison purposes with return value from the fingerprint()
225
self.fingerprint = config["fingerprint"].upper()\
252
self.fingerprint = (config[u"fingerprint"].upper()
227
254
logger.debug(u" Fingerprint: %s", self.fingerprint)
228
if "secret" in config:
229
self.secret = config["secret"].decode(u"base64")
230
elif "secfile" in config:
231
sf = open(config["secfile"])
232
self.secret = sf.read()
255
if u"secret" in config:
256
self.secret = config[u"secret"].decode(u"base64")
257
elif u"secfile" in config:
258
with closing(open(os.path.expanduser
260
(config[u"secfile"])))) as secfile:
261
self.secret = secfile.read()
235
263
raise TypeError(u"No secret or secfile for client %s"
237
self.host = config.get("host", "")
238
self.created = datetime.datetime.now()
265
self.host = config.get(u"host", u"")
266
self.created = datetime.datetime.utcnow()
268
self.last_enabled = None
239
269
self.last_checked_ok = None
240
self.timeout = string_to_delta(config["timeout"])
241
self.interval = string_to_delta(config["interval"])
242
self.stop_hook = stop_hook
270
self.timeout = string_to_delta(config[u"timeout"])
271
self.interval = string_to_delta(config[u"interval"])
272
self.disable_hook = disable_hook
243
273
self.checker = None
244
274
self.checker_initiator_tag = None
245
self.stop_initiator_tag = None
275
self.disable_initiator_tag = None
246
276
self.checker_callback_tag = None
247
self.check_command = config["checker"]
277
self.checker_command = config[u"checker"]
278
self.current_checker_command = None
279
self.last_connect = None
249
282
"""Start this client's checker and timeout hooks"""
283
self.last_enabled = datetime.datetime.utcnow()
250
284
# Schedule a new checker to be started an 'interval' from now,
251
285
# and every interval from then on.
252
self.checker_initiator_tag = gobject.timeout_add\
253
(self._interval_milliseconds,
286
self.checker_initiator_tag = (gobject.timeout_add
287
(self.interval_milliseconds(),
255
289
# Also start a new checker *right now*.
256
290
self.start_checker()
257
# Schedule a stop() when 'timeout' has passed
258
self.stop_initiator_tag = gobject.timeout_add\
259
(self._timeout_milliseconds,
263
The possibility that a client might be restarted is left open,
264
but not currently used."""
265
# If this client doesn't have a secret, it is already stopped.
266
if hasattr(self, "secret") and self.secret:
267
logger.info(u"Stopping client %s", self.name)
291
# Schedule a disable() when 'timeout' has passed
292
self.disable_initiator_tag = (gobject.timeout_add
293
(self.timeout_milliseconds(),
298
"""Disable this client."""
299
if not getattr(self, "enabled", False):
271
if getattr(self, "stop_initiator_tag", False):
272
gobject.source_remove(self.stop_initiator_tag)
273
self.stop_initiator_tag = None
274
if getattr(self, "checker_initiator_tag", False):
301
logger.info(u"Disabling client %s", self.name)
302
if getattr(self, u"disable_initiator_tag", False):
303
gobject.source_remove(self.disable_initiator_tag)
304
self.disable_initiator_tag = None
305
if getattr(self, u"checker_initiator_tag", False):
275
306
gobject.source_remove(self.checker_initiator_tag)
276
307
self.checker_initiator_tag = None
277
308
self.stop_checker()
309
if self.disable_hook:
310
self.disable_hook(self)
280
312
# Do not run this again if called by a gobject.timeout_add
282
315
def __del__(self):
283
self.stop_hook = None
285
def checker_callback(self, pid, condition):
316
self.disable_hook = None
319
def checker_callback(self, pid, condition, command):
286
320
"""The checker has completed, so take appropriate actions."""
287
now = datetime.datetime.now()
288
321
self.checker_callback_tag = None
289
322
self.checker = None
290
if os.WIFEXITED(condition) \
291
and (os.WEXITSTATUS(condition) == 0):
292
logger.info(u"Checker for %(name)s succeeded",
294
self.last_checked_ok = now
295
gobject.source_remove(self.stop_initiator_tag)
296
self.stop_initiator_tag = gobject.timeout_add\
297
(self._timeout_milliseconds,
299
elif not os.WIFEXITED(condition):
323
if os.WIFEXITED(condition):
324
exitstatus = os.WEXITSTATUS(condition)
326
logger.info(u"Checker for %(name)s succeeded",
330
logger.info(u"Checker for %(name)s failed",
300
333
logger.warning(u"Checker for %(name)s crashed?",
303
logger.info(u"Checker for %(name)s failed",
336
def checked_ok(self):
337
"""Bump up the timeout for this client.
339
This should only be called when the client has been seen,
342
self.last_checked_ok = datetime.datetime.utcnow()
343
gobject.source_remove(self.disable_initiator_tag)
344
self.disable_initiator_tag = (gobject.timeout_add
345
(self.timeout_milliseconds(),
305
348
def start_checker(self):
306
349
"""Start a new checker subprocess if one is not running.
307
351
If a checker already exists, leave it running and do
309
353
# The reason for not killing a running checker is that if we
360
430
if error.errno != errno.ESRCH: # No such process
362
432
self.checker = None
363
434
def still_valid(self):
364
435
"""Has the timeout not yet passed for this client?"""
365
now = datetime.datetime.now()
436
if not getattr(self, u"enabled", False):
438
now = datetime.datetime.utcnow()
366
439
if self.last_checked_ok is None:
367
440
return now < (self.created + self.timeout)
369
442
return now < (self.last_checked_ok + self.timeout)
372
def peer_certificate(session):
373
"Return the peer's OpenPGP certificate as a bytestring"
374
# If not an OpenPGP certificate...
375
if gnutls.library.functions.gnutls_certificate_type_get\
376
(session._c_object) \
377
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
378
# ...do the normal thing
379
return session.peer_certificate
380
list_size = ctypes.c_uint()
381
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
382
(session._c_object, ctypes.byref(list_size))
383
if list_size.value == 0:
386
return ctypes.string_at(cert.data, cert.size)
389
def fingerprint(openpgp):
390
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
391
# New GnuTLS "datum" with the OpenPGP public key
392
datum = gnutls.library.types.gnutls_datum_t\
393
(ctypes.cast(ctypes.c_char_p(openpgp),
394
ctypes.POINTER(ctypes.c_ubyte)),
395
ctypes.c_uint(len(openpgp)))
396
# New empty GnuTLS certificate
397
crt = gnutls.library.types.gnutls_openpgp_crt_t()
398
gnutls.library.functions.gnutls_openpgp_crt_init\
400
# Import the OpenPGP public key into the certificate
401
gnutls.library.functions.gnutls_openpgp_crt_import\
402
(crt, ctypes.byref(datum),
403
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
404
# New buffer for the fingerprint
405
buffer = ctypes.create_string_buffer(20)
406
buffer_length = ctypes.c_size_t()
407
# Get the fingerprint from the certificate into the buffer
408
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
409
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
410
# Deinit the certificate
411
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
412
# Convert the buffer to a Python bytestring
413
fpr = ctypes.string_at(buffer, buffer_length.value)
414
# Convert the bytestring to hexadecimal notation
415
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
419
class tcp_handler(SocketServer.BaseRequestHandler, object):
420
"""A TCP request handler class.
421
Instantiated by IPv6_TCPServer for each request to handle it.
445
class ClientDBus(Client, dbus.service.Object):
446
"""A Client class using D-Bus
449
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
451
# dbus.service.Object doesn't use super(), so we can't either.
453
def __init__(self, *args, **kwargs):
454
Client.__init__(self, *args, **kwargs)
455
# Only now, when this client is initialized, can it show up on
457
self.dbus_object_path = (dbus.ObjectPath
459
+ self.name.replace(u".", u"_")))
460
dbus.service.Object.__init__(self, bus,
461
self.dbus_object_path)
463
oldstate = getattr(self, u"enabled", False)
464
r = Client.enable(self)
465
if oldstate != self.enabled:
467
self.PropertyChanged(dbus.String(u"enabled"),
468
dbus.Boolean(True, variant_level=1))
469
self.PropertyChanged(dbus.String(u"last_enabled"),
470
(_datetime_to_dbus(self.last_enabled,
474
def disable(self, signal = True):
475
oldstate = getattr(self, u"enabled", False)
476
r = Client.disable(self)
477
if signal and oldstate != self.enabled:
479
self.PropertyChanged(dbus.String(u"enabled"),
480
dbus.Boolean(False, variant_level=1))
483
def __del__(self, *args, **kwargs):
485
self.remove_from_connection()
488
if hasattr(dbus.service.Object, u"__del__"):
489
dbus.service.Object.__del__(self, *args, **kwargs)
490
Client.__del__(self, *args, **kwargs)
492
def checker_callback(self, pid, condition, command,
494
self.checker_callback_tag = None
497
self.PropertyChanged(dbus.String(u"checker_running"),
498
dbus.Boolean(False, variant_level=1))
499
if os.WIFEXITED(condition):
500
exitstatus = os.WEXITSTATUS(condition)
502
self.CheckerCompleted(dbus.Int16(exitstatus),
503
dbus.Int64(condition),
504
dbus.String(command))
507
self.CheckerCompleted(dbus.Int16(-1),
508
dbus.Int64(condition),
509
dbus.String(command))
511
return Client.checker_callback(self, pid, condition, command,
514
def checked_ok(self, *args, **kwargs):
515
r = Client.checked_ok(self, *args, **kwargs)
517
self.PropertyChanged(
518
dbus.String(u"last_checked_ok"),
519
(_datetime_to_dbus(self.last_checked_ok,
523
def start_checker(self, *args, **kwargs):
524
old_checker = self.checker
525
if self.checker is not None:
526
old_checker_pid = self.checker.pid
528
old_checker_pid = None
529
r = Client.start_checker(self, *args, **kwargs)
530
# Only if new checker process was started
531
if (self.checker is not None
532
and old_checker_pid != self.checker.pid):
534
self.CheckerStarted(self.current_checker_command)
535
self.PropertyChanged(
536
dbus.String(u"checker_running"),
537
dbus.Boolean(True, variant_level=1))
540
def stop_checker(self, *args, **kwargs):
541
old_checker = getattr(self, u"checker", None)
542
r = Client.stop_checker(self, *args, **kwargs)
543
if (old_checker is not None
544
and getattr(self, u"checker", None) is None):
545
self.PropertyChanged(dbus.String(u"checker_running"),
546
dbus.Boolean(False, variant_level=1))
549
## D-Bus methods & signals
550
_interface = u"se.bsnet.fukt.Mandos.Client"
553
@dbus.service.method(_interface)
555
return self.checked_ok()
557
# CheckerCompleted - signal
558
@dbus.service.signal(_interface, signature=u"nxs")
559
def CheckerCompleted(self, exitcode, waitstatus, command):
563
# CheckerStarted - signal
564
@dbus.service.signal(_interface, signature=u"s")
565
def CheckerStarted(self, command):
569
# GetAllProperties - method
570
@dbus.service.method(_interface, out_signature=u"a{sv}")
571
def GetAllProperties(self):
573
return dbus.Dictionary({
574
dbus.String(u"name"):
575
dbus.String(self.name, variant_level=1),
576
dbus.String(u"fingerprint"):
577
dbus.String(self.fingerprint, variant_level=1),
578
dbus.String(u"host"):
579
dbus.String(self.host, variant_level=1),
580
dbus.String(u"created"):
581
_datetime_to_dbus(self.created, variant_level=1),
582
dbus.String(u"last_enabled"):
583
(_datetime_to_dbus(self.last_enabled,
585
if self.last_enabled is not None
586
else dbus.Boolean(False, variant_level=1)),
587
dbus.String(u"enabled"):
588
dbus.Boolean(self.enabled, variant_level=1),
589
dbus.String(u"last_checked_ok"):
590
(_datetime_to_dbus(self.last_checked_ok,
592
if self.last_checked_ok is not None
593
else dbus.Boolean (False, variant_level=1)),
594
dbus.String(u"timeout"):
595
dbus.UInt64(self.timeout_milliseconds(),
597
dbus.String(u"interval"):
598
dbus.UInt64(self.interval_milliseconds(),
600
dbus.String(u"checker"):
601
dbus.String(self.checker_command,
603
dbus.String(u"checker_running"):
604
dbus.Boolean(self.checker is not None,
606
dbus.String(u"object_path"):
607
dbus.ObjectPath(self.dbus_object_path,
611
# IsStillValid - method
612
@dbus.service.method(_interface, out_signature=u"b")
613
def IsStillValid(self):
614
return self.still_valid()
616
# PropertyChanged - signal
617
@dbus.service.signal(_interface, signature=u"sv")
618
def PropertyChanged(self, property, value):
622
# ReceivedSecret - signal
623
@dbus.service.signal(_interface)
624
def ReceivedSecret(self):
629
@dbus.service.signal(_interface)
634
# SetChecker - method
635
@dbus.service.method(_interface, in_signature=u"s")
636
def SetChecker(self, checker):
637
"D-Bus setter method"
638
self.checker_command = checker
640
self.PropertyChanged(dbus.String(u"checker"),
641
dbus.String(self.checker_command,
645
@dbus.service.method(_interface, in_signature=u"s")
646
def SetHost(self, host):
647
"D-Bus setter method"
650
self.PropertyChanged(dbus.String(u"host"),
651
dbus.String(self.host, variant_level=1))
653
# SetInterval - method
654
@dbus.service.method(_interface, in_signature=u"t")
655
def SetInterval(self, milliseconds):
656
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
658
self.PropertyChanged(dbus.String(u"interval"),
659
(dbus.UInt64(self.interval_milliseconds(),
663
@dbus.service.method(_interface, in_signature=u"ay",
665
def SetSecret(self, secret):
666
"D-Bus setter method"
667
self.secret = str(secret)
669
# SetTimeout - method
670
@dbus.service.method(_interface, in_signature=u"t")
671
def SetTimeout(self, milliseconds):
672
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
674
self.PropertyChanged(dbus.String(u"timeout"),
675
(dbus.UInt64(self.timeout_milliseconds(),
679
@dbus.service.method(_interface)
684
# StartChecker - method
685
@dbus.service.method(_interface)
686
def StartChecker(self):
691
@dbus.service.method(_interface)
696
# StopChecker - method
697
@dbus.service.method(_interface)
698
def StopChecker(self):
704
class ClientHandler(socketserver.BaseRequestHandler, object):
705
"""A class to handle client connections.
707
Instantiated once for each connection to handle it.
422
708
Note: This will run in its own forked process."""
424
710
def handle(self):
425
711
logger.info(u"TCP connection from: %s",
426
unicode(self.client_address))
427
session = gnutls.connection.ClientSession\
428
(self.request, gnutls.connection.X509Credentials())
430
line = self.request.makefile().readline()
431
logger.debug(u"Protocol version: %r", line)
433
if int(line.strip().split()[0]) > 1:
435
except (ValueError, IndexError, RuntimeError), error:
436
logger.error(u"Unknown protocol version: %s", error)
439
# Note: gnutls.connection.X509Credentials is really a generic
440
# GnuTLS certificate credentials object so long as no X.509
441
# keys are added to it. Therefore, we can use it here despite
442
# using OpenPGP certificates.
444
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
445
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
447
priority = "NORMAL" # Fallback default, since this
449
if self.server.settings["priority"]:
450
priority = self.server.settings["priority"]
451
gnutls.library.functions.gnutls_priority_set_direct\
452
(session._c_object, priority, None);
456
except gnutls.errors.GNUTLSError, error:
457
logger.warning(u"Handshake failed: %s", error)
458
# Do not run session.bye() here: the session is not
459
# established. Just abandon the request.
462
fpr = fingerprint(peer_certificate(session))
463
except (TypeError, gnutls.errors.GNUTLSError), error:
464
logger.warning(u"Bad certificate: %s", error)
467
logger.debug(u"Fingerprint: %s", fpr)
469
for c in self.server.clients:
470
if c.fingerprint == fpr:
474
logger.warning(u"Client not found for fingerprint: %s",
478
# Have to check if client.still_valid(), since it is possible
479
# that the client timed out while establishing the GnuTLS
481
if not client.still_valid():
482
logger.warning(u"Client %(name)s is invalid",
487
while sent_size < len(client.secret):
488
sent = session.send(client.secret[sent_size:])
489
logger.debug(u"Sent: %d, remaining: %d",
490
sent, len(client.secret)
491
- (sent_size + sent))
496
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
497
"""IPv6 TCP server. Accepts 'None' as address and/or port.
712
unicode(self.client_address))
713
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
714
# Open IPC pipe to parent process
715
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
716
session = (gnutls.connection
717
.ClientSession(self.request,
721
line = self.request.makefile().readline()
722
logger.debug(u"Protocol version: %r", line)
724
if int(line.strip().split()[0]) > 1:
726
except (ValueError, IndexError, RuntimeError), error:
727
logger.error(u"Unknown protocol version: %s", error)
730
# Note: gnutls.connection.X509Credentials is really a
731
# generic GnuTLS certificate credentials object so long as
732
# no X.509 keys are added to it. Therefore, we can use it
733
# here despite using OpenPGP certificates.
735
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
736
# u"+AES-256-CBC", u"+SHA1",
737
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
739
# Use a fallback default, since this MUST be set.
740
priority = self.server.gnutls_priority
743
(gnutls.library.functions
744
.gnutls_priority_set_direct(session._c_object,
749
except gnutls.errors.GNUTLSError, error:
750
logger.warning(u"Handshake failed: %s", error)
751
# Do not run session.bye() here: the session is not
752
# established. Just abandon the request.
754
logger.debug(u"Handshake succeeded")
756
fpr = self.fingerprint(self.peer_certificate(session))
757
except (TypeError, gnutls.errors.GNUTLSError), error:
758
logger.warning(u"Bad certificate: %s", error)
761
logger.debug(u"Fingerprint: %s", fpr)
763
for c in self.server.clients:
764
if c.fingerprint == fpr:
768
ipc.write(u"NOTFOUND %s\n" % fpr)
771
# Have to check if client.still_valid(), since it is
772
# possible that the client timed out while establishing
773
# the GnuTLS session.
774
if not client.still_valid():
775
ipc.write(u"INVALID %s\n" % client.name)
778
ipc.write(u"SENDING %s\n" % client.name)
780
while sent_size < len(client.secret):
781
sent = session.send(client.secret[sent_size:])
782
logger.debug(u"Sent: %d, remaining: %d",
783
sent, len(client.secret)
784
- (sent_size + sent))
789
def peer_certificate(session):
790
"Return the peer's OpenPGP certificate as a bytestring"
791
# If not an OpenPGP certificate...
792
if (gnutls.library.functions
793
.gnutls_certificate_type_get(session._c_object)
794
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
795
# ...do the normal thing
796
return session.peer_certificate
797
list_size = ctypes.c_uint(1)
798
cert_list = (gnutls.library.functions
799
.gnutls_certificate_get_peers
800
(session._c_object, ctypes.byref(list_size)))
801
if not bool(cert_list) and list_size.value != 0:
802
raise gnutls.errors.GNUTLSError(u"error getting peer"
804
if list_size.value == 0:
807
return ctypes.string_at(cert.data, cert.size)
810
def fingerprint(openpgp):
811
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
812
# New GnuTLS "datum" with the OpenPGP public key
813
datum = (gnutls.library.types
814
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
817
ctypes.c_uint(len(openpgp))))
818
# New empty GnuTLS certificate
819
crt = gnutls.library.types.gnutls_openpgp_crt_t()
820
(gnutls.library.functions
821
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
822
# Import the OpenPGP public key into the certificate
823
(gnutls.library.functions
824
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
825
gnutls.library.constants
826
.GNUTLS_OPENPGP_FMT_RAW))
827
# Verify the self signature in the key
828
crtverify = ctypes.c_uint()
829
(gnutls.library.functions
830
.gnutls_openpgp_crt_verify_self(crt, 0,
831
ctypes.byref(crtverify)))
832
if crtverify.value != 0:
833
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
834
raise (gnutls.errors.CertificateSecurityError
836
# New buffer for the fingerprint
837
buf = ctypes.create_string_buffer(20)
838
buf_len = ctypes.c_size_t()
839
# Get the fingerprint from the certificate into the buffer
840
(gnutls.library.functions
841
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
842
ctypes.byref(buf_len)))
843
# Deinit the certificate
844
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
845
# Convert the buffer to a Python bytestring
846
fpr = ctypes.string_at(buf, buf_len.value)
847
# Convert the bytestring to hexadecimal notation
848
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
852
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
853
"""Like socketserver.ForkingMixIn, but also pass a pipe.
855
Assumes a gobject.MainLoop event loop.
857
def process_request(self, request, client_address):
858
"""Overrides and wraps the original process_request().
860
This function creates a new pipe in self.pipe
862
self.pipe = os.pipe()
863
super(ForkingMixInWithPipe,
864
self).process_request(request, client_address)
865
os.close(self.pipe[1]) # close write end
866
# Call "handle_ipc" for both data and EOF events
867
gobject.io_add_watch(self.pipe[0],
868
gobject.IO_IN | gobject.IO_HUP,
870
def handle_ipc(source, condition):
871
"""Dummy function; override as necessary"""
876
class IPv6_TCPServer(ForkingMixInWithPipe,
877
socketserver.TCPServer, object):
878
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
499
settings: Server settings
500
clients: Set() of Client objects
881
enabled: Boolean; whether this server is activated yet
882
interface: None or a network interface name (string)
883
use_ipv6: Boolean; to use IPv6 or not
885
clients: set of Client objects
886
gnutls_priority GnuTLS priority string
887
use_dbus: Boolean; to emit D-Bus signals or not
502
address_family = socket.AF_INET6
503
def __init__(self, *args, **kwargs):
504
if "settings" in kwargs:
505
self.settings = kwargs["settings"]
506
del kwargs["settings"]
507
if "clients" in kwargs:
508
self.clients = kwargs["clients"]
509
del kwargs["clients"]
510
return super(type(self), self).__init__(*args, **kwargs)
889
def __init__(self, server_address, RequestHandlerClass,
890
interface=None, use_ipv6=True, clients=None,
891
gnutls_priority=None, use_dbus=True):
893
self.interface = interface
895
self.address_family = socket.AF_INET6
896
self.clients = clients
897
self.use_dbus = use_dbus
898
self.gnutls_priority = gnutls_priority
899
socketserver.TCPServer.__init__(self, server_address,
511
901
def server_bind(self):
512
902
"""This overrides the normal server_bind() function
513
903
to bind to an interface if one was specified, and also NOT to
514
904
bind to an address or port if they were not specified."""
515
if self.settings["interface"]:
516
# 25 is from /usr/include/asm-i486/socket.h
517
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
905
if self.interface is not None:
519
907
self.socket.setsockopt(socket.SOL_SOCKET,
521
self.settings["interface"])
909
str(self.interface + u'\0'))
522
910
except socket.error, error:
523
911
if error[0] == errno.EPERM:
524
912
logger.error(u"No permission to"
525
913
u" bind to interface %s",
526
self.settings["interface"])
529
917
# Only bind(2) the socket if we really need to.
530
918
if self.server_address[0] or self.server_address[1]:
531
919
if not self.server_address[0]:
533
self.server_address = (in6addr_any,
920
if self.address_family == socket.AF_INET6:
921
any_address = u"::" # in6addr_any
923
any_address = socket.INADDR_ANY
924
self.server_address = (any_address,
534
925
self.server_address[1])
535
926
elif not self.server_address[1]:
536
927
self.server_address = (self.server_address[0],
538
# if self.settings["interface"]:
539
930
# self.server_address = (self.server_address[0],
545
return super(type(self), self).server_bind()
935
return socketserver.TCPServer.server_bind(self)
936
def server_activate(self):
938
return socketserver.TCPServer.server_activate(self)
941
def handle_ipc(self, source, condition, file_objects={}):
943
gobject.IO_IN: u"IN", # There is data to read.
944
gobject.IO_OUT: u"OUT", # Data can be written (without
946
gobject.IO_PRI: u"PRI", # There is urgent data to read.
947
gobject.IO_ERR: u"ERR", # Error condition.
948
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
949
# broken, usually for pipes and
952
conditions_string = ' | '.join(name
954
condition_names.iteritems()
956
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
959
# Turn the pipe file descriptor into a Python file object
960
if source not in file_objects:
961
file_objects[source] = os.fdopen(source, u"r", 1)
963
# Read a line from the file object
964
cmdline = file_objects[source].readline()
965
if not cmdline: # Empty line means end of file
967
file_objects[source].close()
968
del file_objects[source]
970
# Stop calling this function
973
logger.debug(u"IPC command: %r", cmdline)
975
# Parse and act on command
976
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
978
if cmd == u"NOTFOUND":
979
logger.warning(u"Client not found for fingerprint: %s",
983
mandos_dbus_service.ClientNotFound(args)
984
elif cmd == u"INVALID":
985
for client in self.clients:
986
if client.name == args:
987
logger.warning(u"Client %s is invalid", args)
993
logger.error(u"Unknown client %s is invalid", args)
994
elif cmd == u"SENDING":
995
for client in self.clients:
996
if client.name == args:
997
logger.info(u"Sending secret to %s", client.name)
1001
client.ReceivedSecret()
1004
logger.error(u"Sending secret to unknown client %s",
1007
logger.error(u"Unknown IPC command: %r", cmdline)
1009
# Keep calling this function
548
1013
def string_to_delta(interval):
549
1014
"""Parse a string and return a datetime.timedelta
551
>>> string_to_delta('7d')
1016
>>> string_to_delta(u'7d')
552
1017
datetime.timedelta(7)
553
>>> string_to_delta('60s')
1018
>>> string_to_delta(u'60s')
554
1019
datetime.timedelta(0, 60)
555
>>> string_to_delta('60m')
1020
>>> string_to_delta(u'60m')
556
1021
datetime.timedelta(0, 3600)
557
>>> string_to_delta('24h')
1022
>>> string_to_delta(u'24h')
558
1023
datetime.timedelta(1)
559
1024
>>> string_to_delta(u'1w')
560
1025
datetime.timedelta(7)
1026
>>> string_to_delta(u'5m 30s')
1027
datetime.timedelta(0, 330)
563
suffix=unicode(interval[-1])
564
value=int(interval[:-1])
566
delta = datetime.timedelta(value)
568
delta = datetime.timedelta(0, value)
570
delta = datetime.timedelta(0, 0, 0, 0, value)
572
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
574
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1029
timevalue = datetime.timedelta(0)
1030
for s in interval.split():
1032
suffix = unicode(s[-1])
1035
delta = datetime.timedelta(value)
1036
elif suffix == u"s":
1037
delta = datetime.timedelta(0, value)
1038
elif suffix == u"m":
1039
delta = datetime.timedelta(0, 0, 0, 0, value)
1040
elif suffix == u"h":
1041
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1042
elif suffix == u"w":
1043
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1046
except (ValueError, IndexError):
576
1047
raise ValueError
577
except (ValueError, IndexError):
582
1052
def server_state_changed(state):
583
1053
"""Derived from the Avahi example code"""
584
1054
if state == avahi.SERVER_COLLISION:
585
logger.error(u"Server name collision")
1055
logger.error(u"Zeroconf server name collision")
586
1056
service.remove()
587
1057
elif state == avahi.SERVER_RUNNING:
684
1160
# Default values for config file for server-global settings
685
server_defaults = { "interface": "",
690
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
691
"servicename": "Mandos",
1161
server_defaults = { u"interface": u"",
1166
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1167
u"servicename": u"Mandos",
1168
u"use_dbus": u"True",
1169
u"use_ipv6": u"True",
694
1172
# Parse config file for server-global settings
695
server_config = ConfigParser.SafeConfigParser(server_defaults)
1173
server_config = configparser.SafeConfigParser(server_defaults)
696
1174
del server_defaults
697
server_config.read(os.path.join(options.configdir, "mandos.conf"))
698
server_section = "server"
1175
server_config.read(os.path.join(options.configdir,
699
1177
# Convert the SafeConfigParser object to a dict
700
server_settings = dict(server_config.items(server_section))
701
# Use getboolean on the boolean config option
702
server_settings["debug"] = server_config.getboolean\
703
(server_section, "debug")
1178
server_settings = server_config.defaults()
1179
# Use the appropriate methods on the non-string config options
1180
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1181
server_settings[option] = server_config.getboolean(u"DEFAULT",
1183
if server_settings["port"]:
1184
server_settings["port"] = server_config.getint(u"DEFAULT",
704
1186
del server_config
706
1188
# Override the settings from the config file with command line
707
1189
# options, if set.
708
for option in ("interface", "address", "port", "debug",
709
"priority", "servicename", "configdir"):
1190
for option in (u"interface", u"address", u"port", u"debug",
1191
u"priority", u"servicename", u"configdir",
1192
u"use_dbus", u"use_ipv6"):
710
1193
value = getattr(options, option)
711
1194
if value is not None:
712
1195
server_settings[option] = value
1197
# Force all strings to be unicode
1198
for option in server_settings.keys():
1199
if type(server_settings[option]) is str:
1200
server_settings[option] = unicode(server_settings[option])
714
1201
# Now we have our good server settings in "server_settings"
716
debug = server_settings["debug"]
1203
##################################################################
1206
debug = server_settings[u"debug"]
1207
use_dbus = server_settings[u"use_dbus"]
1208
use_ipv6 = server_settings[u"use_ipv6"]
719
1211
syslogger.setLevel(logging.WARNING)
1212
console.setLevel(logging.WARNING)
721
if server_settings["servicename"] != "Mandos":
722
syslogger.setFormatter(logging.Formatter\
723
('Mandos (%s): %%(levelname)s:'
725
% server_settings["servicename"]))
1214
if server_settings[u"servicename"] != u"Mandos":
1215
syslogger.setFormatter(logging.Formatter
1216
(u'Mandos (%s) [%%(process)d]:'
1217
u' %%(levelname)s: %%(message)s'
1218
% server_settings[u"servicename"]))
727
1220
# Parse config file with clients
728
client_defaults = { "timeout": "1h",
730
"checker": "fping -q -- %%(host)s",
1221
client_defaults = { u"timeout": u"1h",
1223
u"checker": u"fping -q -- %%(host)s",
732
client_config = ConfigParser.SafeConfigParser(client_defaults)
733
client_config.read(os.path.join(server_settings["configdir"],
1226
client_config = configparser.SafeConfigParser(client_defaults)
1227
client_config.read(os.path.join(server_settings[u"configdir"],
1230
global mandos_dbus_service
1231
mandos_dbus_service = None
1234
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1235
server_settings[u"port"]),
1238
server_settings[u"interface"],
1242
server_settings[u"priority"],
1244
pidfilename = u"/var/run/mandos.pid"
1246
pidfile = open(pidfilename, u"w")
1248
logger.error(u"Could not open file %r", pidfilename)
1251
uid = pwd.getpwnam(u"_mandos").pw_uid
1252
gid = pwd.getpwnam(u"_mandos").pw_gid
1255
uid = pwd.getpwnam(u"mandos").pw_uid
1256
gid = pwd.getpwnam(u"mandos").pw_gid
1259
uid = pwd.getpwnam(u"nobody").pw_uid
1260
gid = pwd.getpwnam(u"nobody").pw_gid
1267
except OSError, error:
1268
if error[0] != errno.EPERM:
1271
# Enable all possible GnuTLS debugging
1273
# "Use a log level over 10 to enable all debugging options."
1275
gnutls.library.functions.gnutls_global_set_log_level(11)
1277
@gnutls.library.types.gnutls_log_func
1278
def debug_gnutls(level, string):
1279
logger.debug(u"GnuTLS: %s", string[:-1])
1281
(gnutls.library.functions
1282
.gnutls_global_set_log_function(debug_gnutls))
737
service = AvahiService(name = server_settings["servicename"],
738
type = "_mandos._tcp", );
1285
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1286
service = AvahiService(name = server_settings[u"servicename"],
1287
servicetype = u"_mandos._tcp",
1288
protocol = protocol)
739
1289
if server_settings["interface"]:
740
service.interface = if_nametoindex(server_settings["interface"])
1290
service.interface = (if_nametoindex
1291
(str(server_settings[u"interface"])))
742
1293
global main_loop