315
302
"""The checker has completed, so take appropriate actions."""
316
303
self.checker_callback_tag = None
317
304
self.checker = None
320
self.PropertyChanged(dbus.String(u"checker_running"),
321
dbus.Boolean(False, variant_level=1))
322
if (os.WIFEXITED(condition)
323
and (os.WEXITSTATUS(condition) == 0)):
324
logger.info(u"Checker for %(name)s succeeded",
328
self.CheckerCompleted(dbus.Boolean(True),
329
dbus.UInt16(condition),
330
dbus.String(command))
332
elif not os.WIFEXITED(condition):
305
if os.WIFEXITED(condition):
306
exitstatus = os.WEXITSTATUS(condition)
308
logger.info(u"Checker for %(name)s succeeded",
312
logger.info(u"Checker for %(name)s failed",
333
315
logger.warning(u"Checker for %(name)s crashed?",
337
self.CheckerCompleted(dbus.Boolean(False),
338
dbus.UInt16(condition),
339
dbus.String(command))
341
logger.info(u"Checker for %(name)s failed",
345
self.CheckerCompleted(dbus.Boolean(False),
346
dbus.UInt16(condition),
347
dbus.String(command))
349
def bump_timeout(self):
318
def checked_ok(self):
350
319
"""Bump up the timeout for this client.
351
320
This should only be called when the client has been seen,
446
417
return now < (self.created + self.timeout)
448
419
return now < (self.last_checked_ok + self.timeout)
422
class ClientDBus(Client, dbus.service.Object):
423
"""A Client class using D-Bus
425
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
427
# dbus.service.Object doesn't use super(), so we can't either.
429
def __init__(self, *args, **kwargs):
430
Client.__init__(self, *args, **kwargs)
431
# Only now, when this client is initialized, can it show up on
433
self.dbus_object_path = (dbus.ObjectPath
435
+ self.name.replace(".", "_")))
436
dbus.service.Object.__init__(self, bus,
437
self.dbus_object_path)
439
oldstate = getattr(self, "enabled", False)
440
r = Client.enable(self)
441
if oldstate != self.enabled:
443
self.PropertyChanged(dbus.String(u"enabled"),
444
dbus.Boolean(True, variant_level=1))
445
self.PropertyChanged(dbus.String(u"last_enabled"),
446
(_datetime_to_dbus(self.last_enabled,
450
def disable(self, signal = True):
451
oldstate = getattr(self, "enabled", False)
452
r = Client.disable(self)
453
if signal and oldstate != self.enabled:
455
self.PropertyChanged(dbus.String(u"enabled"),
456
dbus.Boolean(False, variant_level=1))
459
def __del__(self, *args, **kwargs):
461
self.remove_from_connection()
462
except org.freedesktop.DBus.Python.LookupError:
464
dbus.service.Object.__del__(self, *args, **kwargs)
465
Client.__del__(self, *args, **kwargs)
467
def checker_callback(self, pid, condition, command,
469
self.checker_callback_tag = None
472
self.PropertyChanged(dbus.String(u"checker_running"),
473
dbus.Boolean(False, variant_level=1))
474
if os.WIFEXITED(condition):
475
exitstatus = os.WEXITSTATUS(condition)
477
self.CheckerCompleted(dbus.Int16(exitstatus),
478
dbus.Int64(condition),
479
dbus.String(command))
482
self.CheckerCompleted(dbus.Int16(-1),
483
dbus.Int64(condition),
484
dbus.String(command))
486
return Client.checker_callback(self, pid, condition, command,
489
def checked_ok(self, *args, **kwargs):
490
r = Client.checked_ok(self, *args, **kwargs)
492
self.PropertyChanged(
493
dbus.String(u"last_checked_ok"),
494
(_datetime_to_dbus(self.last_checked_ok,
498
def start_checker(self, *args, **kwargs):
499
old_checker = self.checker
500
if self.checker is not None:
501
old_checker_pid = self.checker.pid
503
old_checker_pid = None
504
r = Client.start_checker(self, *args, **kwargs)
505
# Only emit D-Bus signal if new checker process was started
506
if ((self.checker is not None)
507
and not (old_checker is not None
508
and old_checker_pid == self.checker.pid)):
509
self.CheckerStarted(self.current_checker_command)
510
self.PropertyChanged(
511
dbus.String("checker_running"),
512
dbus.Boolean(True, variant_level=1))
515
def stop_checker(self, *args, **kwargs):
516
old_checker = getattr(self, "checker", None)
517
r = Client.stop_checker(self, *args, **kwargs)
518
if (old_checker is not None
519
and getattr(self, "checker", None) is None):
520
self.PropertyChanged(dbus.String(u"checker_running"),
521
dbus.Boolean(False, variant_level=1))
450
524
## D-Bus methods & signals
451
_interface = u"org.mandos_system.Mandos.Client"
525
_interface = u"se.bsnet.fukt.Mandos.Client"
453
# BumpTimeout - method
454
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
455
BumpTimeout.__name__ = "BumpTimeout"
528
CheckedOK = dbus.service.method(_interface)(checked_ok)
529
CheckedOK.__name__ = "CheckedOK"
457
531
# CheckerCompleted - signal
458
@dbus.service.signal(_interface, signature="bqs")
459
def CheckerCompleted(self, success, condition, command):
532
@dbus.service.signal(_interface, signature="nxs")
533
def CheckerCompleted(self, exitcode, waitstatus, command):
649
741
def handle(self):
650
742
logger.info(u"TCP connection from: %s",
651
743
unicode(self.client_address))
652
session = (gnutls.connection
653
.ClientSession(self.request,
657
line = self.request.makefile().readline()
658
logger.debug(u"Protocol version: %r", line)
660
if int(line.strip().split()[0]) > 1:
662
except (ValueError, IndexError, RuntimeError), error:
663
logger.error(u"Unknown protocol version: %s", error)
666
# Note: gnutls.connection.X509Credentials is really a generic
667
# GnuTLS certificate credentials object so long as no X.509
668
# keys are added to it. Therefore, we can use it here despite
669
# using OpenPGP certificates.
671
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
672
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
674
# Use a fallback default, since this MUST be set.
675
priority = self.server.settings.get("priority", "NORMAL")
676
(gnutls.library.functions
677
.gnutls_priority_set_direct(session._c_object,
682
except gnutls.errors.GNUTLSError, error:
683
logger.warning(u"Handshake failed: %s", error)
684
# Do not run session.bye() here: the session is not
685
# established. Just abandon the request.
688
fpr = fingerprint(peer_certificate(session))
689
except (TypeError, gnutls.errors.GNUTLSError), error:
690
logger.warning(u"Bad certificate: %s", error)
693
logger.debug(u"Fingerprint: %s", fpr)
694
for c in self.server.clients:
695
if c.fingerprint == fpr:
699
logger.warning(u"Client not found for fingerprint: %s",
703
# Have to check if client.still_valid(), since it is possible
704
# that the client timed out while establishing the GnuTLS
706
if not client.still_valid():
707
logger.warning(u"Client %(name)s is invalid",
711
## This won't work here, since we're in a fork.
712
# client.bump_timeout()
714
while sent_size < len(client.secret):
715
sent = session.send(client.secret[sent_size:])
716
logger.debug(u"Sent: %d, remaining: %d",
717
sent, len(client.secret)
718
- (sent_size + sent))
723
class IPv6_TCPServer(SocketServer.ForkingMixIn,
744
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
745
# Open IPC pipe to parent process
746
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
747
session = (gnutls.connection
748
.ClientSession(self.request,
752
line = self.request.makefile().readline()
753
logger.debug(u"Protocol version: %r", line)
755
if int(line.strip().split()[0]) > 1:
757
except (ValueError, IndexError, RuntimeError), error:
758
logger.error(u"Unknown protocol version: %s", error)
761
# Note: gnutls.connection.X509Credentials is really a
762
# generic GnuTLS certificate credentials object so long as
763
# no X.509 keys are added to it. Therefore, we can use it
764
# here despite using OpenPGP certificates.
766
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
767
# "+AES-256-CBC", "+SHA1",
768
# "+COMP-NULL", "+CTYPE-OPENPGP",
770
# Use a fallback default, since this MUST be set.
771
priority = self.server.settings.get("priority", "NORMAL")
772
(gnutls.library.functions
773
.gnutls_priority_set_direct(session._c_object,
778
except gnutls.errors.GNUTLSError, error:
779
logger.warning(u"Handshake failed: %s", error)
780
# Do not run session.bye() here: the session is not
781
# established. Just abandon the request.
783
logger.debug(u"Handshake succeeded")
785
fpr = fingerprint(peer_certificate(session))
786
except (TypeError, gnutls.errors.GNUTLSError), error:
787
logger.warning(u"Bad certificate: %s", error)
790
logger.debug(u"Fingerprint: %s", fpr)
792
for c in self.server.clients:
793
if c.fingerprint == fpr:
797
logger.warning(u"Client not found for fingerprint: %s",
799
ipc.write("NOTFOUND %s\n" % fpr)
802
# Have to check if client.still_valid(), since it is
803
# possible that the client timed out while establishing
804
# the GnuTLS session.
805
if not client.still_valid():
806
logger.warning(u"Client %(name)s is invalid",
808
ipc.write("INVALID %s\n" % client.name)
811
ipc.write("SENDING %s\n" % client.name)
813
while sent_size < len(client.secret):
814
sent = session.send(client.secret[sent_size:])
815
logger.debug(u"Sent: %d, remaining: %d",
816
sent, len(client.secret)
817
- (sent_size + sent))
822
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
823
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
824
Assumes a gobject.MainLoop event loop.
826
def process_request(self, request, client_address):
827
"""This overrides and wraps the original process_request().
828
This function creates a new pipe in self.pipe
830
self.pipe = os.pipe()
831
super(ForkingMixInWithPipe,
832
self).process_request(request, client_address)
833
os.close(self.pipe[1]) # close write end
834
# Call "handle_ipc" for both data and EOF events
835
gobject.io_add_watch(self.pipe[0],
836
gobject.IO_IN | gobject.IO_HUP,
838
def handle_ipc(source, condition):
839
"""Dummy function; override as necessary"""
844
class IPv6_TCPServer(ForkingMixInWithPipe,
724
845
SocketServer.TCPServer, object):
725
"""IPv6 TCP server. Accepts 'None' as address and/or port.
846
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
727
848
settings: Server settings
728
849
clients: Set() of Client objects
778
906
return super(IPv6_TCPServer, self).server_activate()
779
907
def enable(self):
780
908
self.enabled = True
909
def handle_ipc(self, source, condition, file_objects={}):
911
gobject.IO_IN: "IN", # There is data to read.
912
gobject.IO_OUT: "OUT", # Data can be written (without
914
gobject.IO_PRI: "PRI", # There is urgent data to read.
915
gobject.IO_ERR: "ERR", # Error condition.
916
gobject.IO_HUP: "HUP" # Hung up (the connection has been
917
# broken, usually for pipes and
920
conditions_string = ' | '.join(name
922
condition_names.iteritems()
924
logger.debug("Handling IPC: FD = %d, condition = %s", source,
927
# Turn the pipe file descriptor into a Python file object
928
if source not in file_objects:
929
file_objects[source] = os.fdopen(source, "r", 1)
931
# Read a line from the file object
932
cmdline = file_objects[source].readline()
933
if not cmdline: # Empty line means end of file
935
file_objects[source].close()
936
del file_objects[source]
938
# Stop calling this function
941
logger.debug("IPC command: %r\n" % cmdline)
943
# Parse and act on command
944
cmd, args = cmdline.split(None, 1)
945
if cmd == "NOTFOUND":
946
if self.settings["use_dbus"]:
948
mandos_dbus_service.ClientNotFound(args)
949
elif cmd == "INVALID":
950
if self.settings["use_dbus"]:
951
for client in self.clients:
952
if client.name == args:
956
elif cmd == "SENDING":
957
for client in self.clients:
958
if client.name == args:
960
if self.settings["use_dbus"]:
962
client.ReceivedSecret()
965
logger.error("Unknown IPC command: %r", cmdline)
967
# Keep calling this function
783
971
def string_to_delta(interval):
784
972
"""Parse a string and return a datetime.timedelta
786
974
>>> string_to_delta('7d')
787
975
datetime.timedelta(7)
788
976
>>> string_to_delta('60s')
937
1132
server_config.read(os.path.join(options.configdir, "mandos.conf"))
938
1133
# Convert the SafeConfigParser object to a dict
939
1134
server_settings = server_config.defaults()
940
# Use getboolean on the boolean config options
941
server_settings["debug"] = (server_config.getboolean
942
("DEFAULT", "debug"))
943
server_settings["use_dbus"] = (server_config.getboolean
944
("DEFAULT", "use_dbus"))
1135
# Use the appropriate methods on the non-string config options
1136
server_settings["debug"] = server_config.getboolean("DEFAULT",
1138
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1140
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1142
if server_settings["port"]:
1143
server_settings["port"] = server_config.getint("DEFAULT",
945
1145
del server_config
947
1147
# Override the settings from the config file with command line
948
1148
# options, if set.
949
1149
for option in ("interface", "address", "port", "debug",
950
1150
"priority", "servicename", "configdir",
1151
"use_dbus", "use_ipv6"):
952
1152
value = getattr(options, option)
953
1153
if value is not None:
954
1154
server_settings[option] = value
956
1156
# Now we have our good server settings in "server_settings"
1158
##################################################################
958
1160
# For convenience
959
1161
debug = server_settings["debug"]
960
1162
use_dbus = server_settings["use_dbus"]
1163
use_ipv6 = server_settings["use_ipv6"]
963
1166
syslogger.setLevel(logging.WARNING)
966
1169
if server_settings["servicename"] != "Mandos":
967
1170
syslogger.setFormatter(logging.Formatter
968
('Mandos (%s): %%(levelname)s:'
1171
('Mandos (%s) [%%(process)d]:'
1172
' %%(levelname)s: %%(message)s'
970
1173
% server_settings["servicename"]))
972
1175
# Parse config file with clients
973
1176
client_defaults = { "timeout": "1h",
974
1177
"interval": "5m",
975
"checker": "fping -q -- %(host)s",
1178
"checker": "fping -q -- %%(host)s",
978
1181
client_config = ConfigParser.SafeConfigParser(client_defaults)
979
1182
client_config.read(os.path.join(server_settings["configdir"],
980
1183
"clients.conf"))
1185
global mandos_dbus_service
1186
mandos_dbus_service = None
983
1189
tcp_server = IPv6_TCPServer((server_settings["address"],
984
1190
server_settings["port"]),
986
1192
settings=server_settings,
1193
clients=clients, use_ipv6=use_ipv6)
988
1194
pidfilename = "/var/run/mandos.pid"
990
1196
pidfile = open(pidfilename, "w")
991
except IOError, error:
992
1198
logger.error("Could not open file %r", pidfilename)
995
1201
uid = pwd.getpwnam("_mandos").pw_uid
1202
gid = pwd.getpwnam("_mandos").pw_gid
996
1203
except KeyError:
998
1205
uid = pwd.getpwnam("mandos").pw_uid
1206
gid = pwd.getpwnam("mandos").pw_gid
999
1207
except KeyError:
1001
1209
uid = pwd.getpwnam("nobody").pw_uid
1210
gid = pwd.getpwnam("nogroup").pw_gid
1002
1211
except KeyError:
1005
gid = pwd.getpwnam("_mandos").pw_gid
1008
gid = pwd.getpwnam("mandos").pw_gid
1011
gid = pwd.getpwnam("nogroup").pw_gid
1017
1217
except OSError, error:
1018
1218
if error[0] != errno.EPERM:
1221
# Enable all possible GnuTLS debugging
1223
# "Use a log level over 10 to enable all debugging options."
1225
gnutls.library.functions.gnutls_global_set_log_level(11)
1227
@gnutls.library.types.gnutls_log_func
1228
def debug_gnutls(level, string):
1229
logger.debug("GnuTLS: %s", string[:-1])
1231
(gnutls.library.functions
1232
.gnutls_global_set_log_function(debug_gnutls))
1235
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1022
1236
service = AvahiService(name = server_settings["servicename"],
1023
servicetype = "_mandos._tcp", )
1237
servicetype = "_mandos._tcp",
1238
protocol = protocol)
1024
1239
if server_settings["interface"]:
1025
1240
service.interface = (if_nametoindex
1026
1241
(server_settings["interface"]))
1096
1311
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1099
class MandosServer(dbus.service.Object):
1314
class MandosDBusService(dbus.service.Object):
1100
1315
"""A D-Bus proxy object"""
1101
1316
def __init__(self):
1102
dbus.service.Object.__init__(self, bus,
1104
_interface = u"org.mandos_system.Mandos"
1317
dbus.service.Object.__init__(self, bus, "/")
1318
_interface = u"se.bsnet.fukt.Mandos"
1106
1320
@dbus.service.signal(_interface, signature="oa{sv}")
1107
1321
def ClientAdded(self, objpath, properties):
1111
@dbus.service.signal(_interface, signature="o")
1112
def ClientRemoved(self, objpath):
1325
@dbus.service.signal(_interface, signature="s")
1326
def ClientNotFound(self, fingerprint):
1330
@dbus.service.signal(_interface, signature="os")
1331
def ClientRemoved(self, objpath, name):
1116
1335
@dbus.service.method(_interface, out_signature="ao")
1117
1336
def GetAllClients(self):
1118
1338
return dbus.Array(c.dbus_object_path for c in clients)
1120
1340
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1121
1341
def GetAllClientsWithProperties(self):
1122
1343
return dbus.Dictionary(
1123
1344
((c.dbus_object_path, c.GetAllProperties())
1124
1345
for c in clients),
1125
1346
signature="oa{sv}")
1127
1348
@dbus.service.method(_interface, in_signature="o")
1128
1349
def RemoveClient(self, object_path):
1129
1351
for c in clients:
1130
1352
if c.dbus_object_path == object_path:
1131
1353
clients.remove(c)
1354
c.remove_from_connection()
1132
1355
# Don't signal anything except ClientRemoved
1356
c.disable(signal=False)
1135
1357
# Emit D-Bus signal
1136
self.ClientRemoved(object_path)
1358
self.ClientRemoved(object_path, c.name)
1142
mandos_server = MandosServer()
1364
mandos_dbus_service = MandosDBusService()
1144
1366
for client in clients:
1146
1368
# Emit D-Bus signal
1147
mandos_server.ClientAdded(client.dbus_object_path,
1148
client.GetAllProperties())
1369
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1370
client.GetAllProperties())
1149
1371
client.enable()
1151
1373
tcp_server.enable()