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()
464
if hasattr(dbus.service.Object, "__del__"):
465
dbus.service.Object.__del__(self, *args, **kwargs)
466
Client.__del__(self, *args, **kwargs)
468
def checker_callback(self, pid, condition, command,
470
self.checker_callback_tag = None
473
self.PropertyChanged(dbus.String(u"checker_running"),
474
dbus.Boolean(False, variant_level=1))
475
if os.WIFEXITED(condition):
476
exitstatus = os.WEXITSTATUS(condition)
478
self.CheckerCompleted(dbus.Int16(exitstatus),
479
dbus.Int64(condition),
480
dbus.String(command))
483
self.CheckerCompleted(dbus.Int16(-1),
484
dbus.Int64(condition),
485
dbus.String(command))
487
return Client.checker_callback(self, pid, condition, command,
490
def checked_ok(self, *args, **kwargs):
491
r = Client.checked_ok(self, *args, **kwargs)
493
self.PropertyChanged(
494
dbus.String(u"last_checked_ok"),
495
(_datetime_to_dbus(self.last_checked_ok,
499
def start_checker(self, *args, **kwargs):
500
old_checker = self.checker
501
if self.checker is not None:
502
old_checker_pid = self.checker.pid
504
old_checker_pid = None
505
r = Client.start_checker(self, *args, **kwargs)
506
# Only if new checker process was started
507
if (self.checker is not None
508
and old_checker_pid != self.checker.pid):
510
self.CheckerStarted(self.current_checker_command)
511
self.PropertyChanged(
512
dbus.String("checker_running"),
513
dbus.Boolean(True, variant_level=1))
516
def stop_checker(self, *args, **kwargs):
517
old_checker = getattr(self, "checker", None)
518
r = Client.stop_checker(self, *args, **kwargs)
519
if (old_checker is not None
520
and getattr(self, "checker", None) is None):
521
self.PropertyChanged(dbus.String(u"checker_running"),
522
dbus.Boolean(False, variant_level=1))
450
525
## D-Bus methods & signals
451
_interface = u"org.mandos_system.Mandos.Client"
526
_interface = u"se.bsnet.fukt.Mandos.Client"
453
# BumpTimeout - method
454
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
455
BumpTimeout.__name__ = "BumpTimeout"
529
CheckedOK = dbus.service.method(_interface)(checked_ok)
530
CheckedOK.__name__ = "CheckedOK"
457
532
# CheckerCompleted - signal
458
@dbus.service.signal(_interface, signature="bqs")
459
def CheckerCompleted(self, success, condition, command):
533
@dbus.service.signal(_interface, signature="nxs")
534
def CheckerCompleted(self, exitcode, waitstatus, command):
649
742
def handle(self):
650
743
logger.info(u"TCP connection from: %s",
651
744
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,
745
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
746
# Open IPC pipe to parent process
747
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
748
session = (gnutls.connection
749
.ClientSession(self.request,
753
line = self.request.makefile().readline()
754
logger.debug(u"Protocol version: %r", line)
756
if int(line.strip().split()[0]) > 1:
758
except (ValueError, IndexError, RuntimeError), error:
759
logger.error(u"Unknown protocol version: %s", error)
762
# Note: gnutls.connection.X509Credentials is really a
763
# generic GnuTLS certificate credentials object so long as
764
# no X.509 keys are added to it. Therefore, we can use it
765
# here despite using OpenPGP certificates.
767
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
768
# "+AES-256-CBC", "+SHA1",
769
# "+COMP-NULL", "+CTYPE-OPENPGP",
771
# Use a fallback default, since this MUST be set.
772
priority = self.server.settings.get("priority", "NORMAL")
773
(gnutls.library.functions
774
.gnutls_priority_set_direct(session._c_object,
779
except gnutls.errors.GNUTLSError, error:
780
logger.warning(u"Handshake failed: %s", error)
781
# Do not run session.bye() here: the session is not
782
# established. Just abandon the request.
784
logger.debug(u"Handshake succeeded")
786
fpr = fingerprint(peer_certificate(session))
787
except (TypeError, gnutls.errors.GNUTLSError), error:
788
logger.warning(u"Bad certificate: %s", error)
791
logger.debug(u"Fingerprint: %s", fpr)
793
for c in self.server.clients:
794
if c.fingerprint == fpr:
798
logger.warning(u"Client not found for fingerprint: %s",
800
ipc.write("NOTFOUND %s\n" % fpr)
803
# Have to check if client.still_valid(), since it is
804
# possible that the client timed out while establishing
805
# the GnuTLS session.
806
if not client.still_valid():
807
logger.warning(u"Client %(name)s is invalid",
809
ipc.write("INVALID %s\n" % client.name)
812
ipc.write("SENDING %s\n" % client.name)
814
while sent_size < len(client.secret):
815
sent = session.send(client.secret[sent_size:])
816
logger.debug(u"Sent: %d, remaining: %d",
817
sent, len(client.secret)
818
- (sent_size + sent))
823
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
824
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
825
Assumes a gobject.MainLoop event loop.
827
def process_request(self, request, client_address):
828
"""This overrides and wraps the original process_request().
829
This function creates a new pipe in self.pipe
831
self.pipe = os.pipe()
832
super(ForkingMixInWithPipe,
833
self).process_request(request, client_address)
834
os.close(self.pipe[1]) # close write end
835
# Call "handle_ipc" for both data and EOF events
836
gobject.io_add_watch(self.pipe[0],
837
gobject.IO_IN | gobject.IO_HUP,
839
def handle_ipc(source, condition):
840
"""Dummy function; override as necessary"""
845
class IPv6_TCPServer(ForkingMixInWithPipe,
724
846
SocketServer.TCPServer, object):
725
"""IPv6 TCP server. Accepts 'None' as address and/or port.
847
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
727
849
settings: Server settings
728
850
clients: Set() of Client objects
778
907
return super(IPv6_TCPServer, self).server_activate()
779
908
def enable(self):
780
909
self.enabled = True
910
def handle_ipc(self, source, condition, file_objects={}):
912
gobject.IO_IN: "IN", # There is data to read.
913
gobject.IO_OUT: "OUT", # Data can be written (without
915
gobject.IO_PRI: "PRI", # There is urgent data to read.
916
gobject.IO_ERR: "ERR", # Error condition.
917
gobject.IO_HUP: "HUP" # Hung up (the connection has been
918
# broken, usually for pipes and
921
conditions_string = ' | '.join(name
923
condition_names.iteritems()
925
logger.debug("Handling IPC: FD = %d, condition = %s", source,
928
# Turn the pipe file descriptor into a Python file object
929
if source not in file_objects:
930
file_objects[source] = os.fdopen(source, "r", 1)
932
# Read a line from the file object
933
cmdline = file_objects[source].readline()
934
if not cmdline: # Empty line means end of file
936
file_objects[source].close()
937
del file_objects[source]
939
# Stop calling this function
942
logger.debug("IPC command: %r\n" % cmdline)
944
# Parse and act on command
945
cmd, args = cmdline.split(None, 1)
946
if cmd == "NOTFOUND":
947
if self.settings["use_dbus"]:
949
mandos_dbus_service.ClientNotFound(args)
950
elif cmd == "INVALID":
951
if self.settings["use_dbus"]:
952
for client in self.clients:
953
if client.name == args:
957
elif cmd == "SENDING":
958
for client in self.clients:
959
if client.name == args:
961
if self.settings["use_dbus"]:
963
client.ReceivedSecret()
966
logger.error("Unknown IPC command: %r", cmdline)
968
# Keep calling this function
783
972
def string_to_delta(interval):
784
973
"""Parse a string and return a datetime.timedelta
786
975
>>> string_to_delta('7d')
787
976
datetime.timedelta(7)
788
977
>>> string_to_delta('60s')
936
1133
server_config.read(os.path.join(options.configdir, "mandos.conf"))
937
1134
# Convert the SafeConfigParser object to a dict
938
1135
server_settings = server_config.defaults()
939
# Use getboolean on the boolean config options
940
server_settings["debug"] = (server_config.getboolean
941
("DEFAULT", "debug"))
942
server_settings["use_dbus"] = (server_config.getboolean
943
("DEFAULT", "use_dbus"))
1136
# Use the appropriate methods on the non-string config options
1137
server_settings["debug"] = server_config.getboolean("DEFAULT",
1139
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1141
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1143
if server_settings["port"]:
1144
server_settings["port"] = server_config.getint("DEFAULT",
944
1146
del server_config
946
1148
# Override the settings from the config file with command line
947
1149
# options, if set.
948
1150
for option in ("interface", "address", "port", "debug",
949
1151
"priority", "servicename", "configdir",
1152
"use_dbus", "use_ipv6"):
951
1153
value = getattr(options, option)
952
1154
if value is not None:
953
1155
server_settings[option] = value
955
1157
# Now we have our good server settings in "server_settings"
1159
##################################################################
957
1161
# For convenience
958
1162
debug = server_settings["debug"]
959
1163
use_dbus = server_settings["use_dbus"]
1164
use_ipv6 = server_settings["use_ipv6"]
963
1167
syslogger.setLevel(logging.WARNING)
966
1170
if server_settings["servicename"] != "Mandos":
967
1171
syslogger.setFormatter(logging.Formatter
968
('Mandos (%s): %%(levelname)s:'
1172
('Mandos (%s) [%%(process)d]:'
1173
' %%(levelname)s: %%(message)s'
970
1174
% server_settings["servicename"]))
972
1176
# Parse config file with clients
973
1177
client_defaults = { "timeout": "1h",
974
1178
"interval": "5m",
975
"checker": "fping -q -- %(host)s",
1179
"checker": "fping -q -- %%(host)s",
978
1182
client_config = ConfigParser.SafeConfigParser(client_defaults)
979
1183
client_config.read(os.path.join(server_settings["configdir"],
980
1184
"clients.conf"))
1186
global mandos_dbus_service
1187
mandos_dbus_service = None
983
1190
tcp_server = IPv6_TCPServer((server_settings["address"],
984
1191
server_settings["port"]),
986
1193
settings=server_settings,
1194
clients=clients, use_ipv6=use_ipv6)
988
1195
pidfilename = "/var/run/mandos.pid"
990
1197
pidfile = open(pidfilename, "w")
991
except IOError, error:
992
1199
logger.error("Could not open file %r", pidfilename)
995
1202
uid = pwd.getpwnam("_mandos").pw_uid
1203
gid = pwd.getpwnam("_mandos").pw_gid
996
1204
except KeyError:
998
1206
uid = pwd.getpwnam("mandos").pw_uid
1207
gid = pwd.getpwnam("mandos").pw_gid
999
1208
except KeyError:
1001
1210
uid = pwd.getpwnam("nobody").pw_uid
1211
gid = pwd.getpwnam("nogroup").pw_gid
1002
1212
except KeyError:
1005
gid = pwd.getpwnam("_mandos").pw_gid
1008
gid = pwd.getpwnam("mandos").pw_gid
1011
gid = pwd.getpwnam("nogroup").pw_gid
1017
1218
except OSError, error:
1018
1219
if error[0] != errno.EPERM:
1222
# Enable all possible GnuTLS debugging
1224
# "Use a log level over 10 to enable all debugging options."
1226
gnutls.library.functions.gnutls_global_set_log_level(11)
1228
@gnutls.library.types.gnutls_log_func
1229
def debug_gnutls(level, string):
1230
logger.debug("GnuTLS: %s", string[:-1])
1232
(gnutls.library.functions
1233
.gnutls_global_set_log_function(debug_gnutls))
1236
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1022
1237
service = AvahiService(name = server_settings["servicename"],
1023
servicetype = "_mandos._tcp", )
1238
servicetype = "_mandos._tcp",
1239
protocol = protocol)
1024
1240
if server_settings["interface"]:
1025
1241
service.interface = (if_nametoindex
1026
1242
(server_settings["interface"]))
1095
1312
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1098
class MandosServer(dbus.service.Object):
1315
class MandosDBusService(dbus.service.Object):
1099
1316
"""A D-Bus proxy object"""
1100
1317
def __init__(self):
1101
dbus.service.Object.__init__(self, bus,
1103
_interface = u"org.mandos_system.Mandos"
1318
dbus.service.Object.__init__(self, bus, "/")
1319
_interface = u"se.bsnet.fukt.Mandos"
1105
1321
@dbus.service.signal(_interface, signature="oa{sv}")
1106
1322
def ClientAdded(self, objpath, properties):
1110
@dbus.service.signal(_interface, signature="o")
1111
def ClientRemoved(self, objpath):
1326
@dbus.service.signal(_interface, signature="s")
1327
def ClientNotFound(self, fingerprint):
1331
@dbus.service.signal(_interface, signature="os")
1332
def ClientRemoved(self, objpath, name):
1115
1336
@dbus.service.method(_interface, out_signature="ao")
1116
1337
def GetAllClients(self):
1117
1339
return dbus.Array(c.dbus_object_path for c in clients)
1119
1341
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1120
1342
def GetAllClientsWithProperties(self):
1121
1344
return dbus.Dictionary(
1122
1345
((c.dbus_object_path, c.GetAllProperties())
1123
1346
for c in clients),
1124
1347
signature="oa{sv}")
1126
1349
@dbus.service.method(_interface, in_signature="o")
1127
1350
def RemoveClient(self, object_path):
1128
1352
for c in clients:
1129
1353
if c.dbus_object_path == object_path:
1130
1354
clients.remove(c)
1355
c.remove_from_connection()
1131
1356
# Don't signal anything except ClientRemoved
1357
c.disable(signal=False)
1134
1358
# Emit D-Bus signal
1135
self.ClientRemoved(object_path)
1359
self.ClientRemoved(object_path, c.name)
1138
@dbus.service.method(_interface)
1144
mandos_server = MandosServer()
1365
mandos_dbus_service = MandosDBusService()
1146
1367
for client in clients:
1148
1369
# Emit D-Bus signal
1149
mandos_server.ClientAdded(client.dbus_object_path,
1150
client.GetAllProperties())
1370
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1371
client.GetAllProperties())
1151
1372
client.enable()
1153
1374
tcp_server.enable()