71
71
logger = logging.Logger('mandos')
72
72
syslogger = (logging.handlers.SysLogHandler
73
73
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
74
74
address = "/dev/log"))
75
75
syslogger.setFormatter(logging.Formatter
76
('Mandos [%(process)d]: %(levelname)s:'
76
('Mandos: %(levelname)s: %(message)s'))
78
77
logger.addHandler(syslogger)
80
79
console = logging.StreamHandler()
81
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
82
' %(levelname)s: %(message)s'))
80
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
83
82
logger.addHandler(console)
85
84
class AvahiError(Exception):
86
def __init__(self, value, *args, **kwargs):
85
def __init__(self, value):
88
super(AvahiError, self).__init__(value, *args, **kwargs)
89
def __unicode__(self):
90
return unicode(repr(self.value))
87
super(AvahiError, self).__init__()
89
return repr(self.value)
92
91
class AvahiServiceError(AvahiError):
173
170
# End of Avahi example code
176
def _datetime_to_dbus(dt, variant_level=0):
177
"""Convert a UTC datetime.datetime() to a D-Bus type."""
178
return dbus.String(dt.isoformat(), variant_level=variant_level)
173
def _datetime_to_dbus_struct(dt, variant_level=0):
174
"""Convert a UTC datetime.datetime() to a D-Bus struct.
175
The format is special to this application, since we could not find
176
any other standard way."""
177
return dbus.Struct((dbus.Int16(dt.year),
181
dbus.Byte(dt.minute),
182
dbus.Byte(dt.second),
183
dbus.UInt32(dt.microsecond)),
185
variant_level=variant_level)
181
188
class Client(dbus.service.Object):
182
189
"""A representation of a client host served by this server.
184
name: string; from the config file, used in log messages and
191
name: string; from the config file, used in log messages
186
192
fingerprint: string (40 or 32 hexadecimal digits); used to
187
193
uniquely identify the client
188
194
secret: bytestring; sent verbatim (over TLS) to client
189
195
host: string; available for use by the checker command
190
196
created: datetime.datetime(); (UTC) object creation
191
last_enabled: datetime.datetime(); (UTC)
197
last_started: datetime.datetime(); (UTC)
193
199
last_checked_ok: datetime.datetime(); (UTC) or None
194
200
timeout: datetime.timedelta(); How long from last_checked_ok
195
201
until this client is invalid
196
202
interval: datetime.timedelta(); How often to start a new checker
197
disable_hook: If set, called by disable() as disable_hook(self)
203
stop_hook: If set, called by stop() as stop_hook(self)
198
204
checker: subprocess.Popen(); a running checker process used
199
205
to see if the client lives.
200
206
'None' if no process is running.
201
207
checker_initiator_tag: a gobject event source tag, or None
202
disable_initiator_tag: - '' -
208
stop_initiator_tag: - '' -
203
209
checker_callback_tag: - '' -
204
210
checker_command: string; External command which is run to check if
205
211
client lives. %() expansions are done at
206
212
runtime with vars(self) as dict, so that for
207
213
instance %(name)s can be used in the command.
208
current_checker_command: string; current running checker_command
209
use_dbus: bool(); Whether to provide D-Bus interface and signals
210
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
214
dbus_object_path: dbus.ObjectPath
216
_timeout: Real variable for 'timeout'
217
_interval: Real variable for 'interval'
218
_timeout_milliseconds: Used when calling gobject.timeout_add()
219
_interval_milliseconds: - '' -
212
def timeout_milliseconds(self):
213
"Return the 'timeout' attribute in milliseconds"
214
return ((self.timeout.days * 24 * 60 * 60 * 1000)
215
+ (self.timeout.seconds * 1000)
216
+ (self.timeout.microseconds // 1000))
218
def interval_milliseconds(self):
219
"Return the 'interval' attribute in milliseconds"
220
return ((self.interval.days * 24 * 60 * 60 * 1000)
221
+ (self.interval.seconds * 1000)
222
+ (self.interval.microseconds // 1000))
224
def __init__(self, name = None, disable_hook=None, config=None,
221
def _set_timeout(self, timeout):
222
"Setter function for the 'timeout' attribute"
223
self._timeout = timeout
224
self._timeout_milliseconds = ((self.timeout.days
225
* 24 * 60 * 60 * 1000)
226
+ (self.timeout.seconds * 1000)
227
+ (self.timeout.microseconds
230
self.PropertyChanged(dbus.String(u"timeout"),
231
(dbus.UInt64(self._timeout_milliseconds,
233
timeout = property(lambda self: self._timeout, _set_timeout)
236
def _set_interval(self, interval):
237
"Setter function for the 'interval' attribute"
238
self._interval = interval
239
self._interval_milliseconds = ((self.interval.days
240
* 24 * 60 * 60 * 1000)
241
+ (self.interval.seconds
243
+ (self.interval.microseconds
246
self.PropertyChanged(dbus.String(u"interval"),
247
(dbus.UInt64(self._interval_milliseconds,
249
interval = property(lambda self: self._interval, _set_interval)
252
def __init__(self, name = None, stop_hook=None, config=None):
226
253
"""Note: the 'checker' key in 'config' sets the
227
254
'checker_command' attribute and *not* the 'checker'
256
self.dbus_object_path = (dbus.ObjectPath
258
+ name.replace(".", "_")))
259
dbus.service.Object.__init__(self, bus,
260
self.dbus_object_path)
230
261
if config is None:
232
264
logger.debug(u"Creating client %r", self.name)
233
self.use_dbus = False # During __init__
234
265
# Uppercase and remove spaces from fingerprint for later
235
266
# comparison purposes with return value from the fingerprint()
250
281
self.host = config.get("host", "")
251
282
self.created = datetime.datetime.utcnow()
253
self.last_enabled = None
284
self.last_started = None
254
285
self.last_checked_ok = None
255
286
self.timeout = string_to_delta(config["timeout"])
256
287
self.interval = string_to_delta(config["interval"])
257
self.disable_hook = disable_hook
288
self.stop_hook = stop_hook
258
289
self.checker = None
259
290
self.checker_initiator_tag = None
260
self.disable_initiator_tag = None
291
self.stop_initiator_tag = None
261
292
self.checker_callback_tag = None
262
293
self.checker_command = config["checker"]
263
self.current_checker_command = None
264
self.last_connect = None
265
# Only now, when this client is initialized, can it show up on
267
self.use_dbus = use_dbus
269
self.dbus_object_path = (dbus.ObjectPath
271
+ self.name.replace(".", "_")))
272
dbus.service.Object.__init__(self, bus,
273
self.dbus_object_path)
276
296
"""Start this client's checker and timeout hooks"""
277
self.last_enabled = datetime.datetime.utcnow()
297
self.last_started = datetime.datetime.utcnow()
278
298
# Schedule a new checker to be started an 'interval' from now,
279
299
# and every interval from then on.
280
300
self.checker_initiator_tag = (gobject.timeout_add
281
(self.interval_milliseconds(),
301
(self._interval_milliseconds,
282
302
self.start_checker))
283
303
# Also start a new checker *right now*.
284
304
self.start_checker()
285
# Schedule a disable() when 'timeout' has passed
286
self.disable_initiator_tag = (gobject.timeout_add
287
(self.timeout_milliseconds(),
292
self.PropertyChanged(dbus.String(u"enabled"),
293
dbus.Boolean(True, variant_level=1))
294
self.PropertyChanged(dbus.String(u"last_enabled"),
295
(_datetime_to_dbus(self.last_enabled,
305
# Schedule a stop() when 'timeout' has passed
306
self.stop_initiator_tag = (gobject.timeout_add
307
(self._timeout_milliseconds,
311
self.PropertyChanged(dbus.String(u"started"),
312
dbus.Boolean(True, variant_level=1))
313
self.PropertyChanged(dbus.String(u"last_started"),
314
(_datetime_to_dbus_struct
315
(self.last_started, variant_level=1)))
299
"""Disable this client."""
300
if not getattr(self, "enabled", False):
318
"""Stop this client."""
319
if not getattr(self, "started", False):
302
logger.info(u"Disabling client %s", self.name)
303
if getattr(self, "disable_initiator_tag", False):
304
gobject.source_remove(self.disable_initiator_tag)
305
self.disable_initiator_tag = None
321
logger.info(u"Stopping client %s", self.name)
322
if getattr(self, "stop_initiator_tag", False):
323
gobject.source_remove(self.stop_initiator_tag)
324
self.stop_initiator_tag = None
306
325
if getattr(self, "checker_initiator_tag", False):
307
326
gobject.source_remove(self.checker_initiator_tag)
308
327
self.checker_initiator_tag = None
309
328
self.stop_checker()
310
if self.disable_hook:
311
self.disable_hook(self)
315
self.PropertyChanged(dbus.String(u"enabled"),
316
dbus.Boolean(False, variant_level=1))
333
self.PropertyChanged(dbus.String(u"started"),
334
dbus.Boolean(False, variant_level=1))
317
335
# Do not run this again if called by a gobject.timeout_add
320
338
def __del__(self):
321
self.disable_hook = None
339
self.stop_hook = None
324
342
def checker_callback(self, pid, condition, command):
325
343
"""The checker has completed, so take appropriate actions."""
326
344
self.checker_callback_tag = None
327
345
self.checker = None
347
self.PropertyChanged(dbus.String(u"checker_running"),
348
dbus.Boolean(False, variant_level=1))
349
if (os.WIFEXITED(condition)
350
and (os.WEXITSTATUS(condition) == 0)):
351
logger.info(u"Checker for %(name)s succeeded",
329
353
# Emit D-Bus signal
330
self.PropertyChanged(dbus.String(u"checker_running"),
331
dbus.Boolean(False, variant_level=1))
332
if os.WIFEXITED(condition):
333
exitstatus = os.WEXITSTATUS(condition)
335
logger.info(u"Checker for %(name)s succeeded",
339
logger.info(u"Checker for %(name)s failed",
343
self.CheckerCompleted(dbus.Int16(exitstatus),
344
dbus.Int64(condition),
345
dbus.String(command))
354
self.CheckerCompleted(dbus.Boolean(True),
355
dbus.UInt16(condition),
356
dbus.String(command))
358
elif not os.WIFEXITED(condition):
347
359
logger.warning(u"Checker for %(name)s crashed?",
351
self.CheckerCompleted(dbus.Int16(-1),
352
dbus.Int64(condition),
353
dbus.String(command))
362
self.CheckerCompleted(dbus.Boolean(False),
363
dbus.UInt16(condition),
364
dbus.String(command))
366
logger.info(u"Checker for %(name)s failed",
369
self.CheckerCompleted(dbus.Boolean(False),
370
dbus.UInt16(condition),
371
dbus.String(command))
355
def checked_ok(self):
373
def bump_timeout(self):
356
374
"""Bump up the timeout for this client.
357
375
This should only be called when the client has been seen,
360
378
self.last_checked_ok = datetime.datetime.utcnow()
361
gobject.source_remove(self.disable_initiator_tag)
362
self.disable_initiator_tag = (gobject.timeout_add
363
(self.timeout_milliseconds(),
367
self.PropertyChanged(
368
dbus.String(u"last_checked_ok"),
369
(_datetime_to_dbus(self.last_checked_ok,
379
gobject.source_remove(self.stop_initiator_tag)
380
self.stop_initiator_tag = (gobject.timeout_add
381
(self._timeout_milliseconds,
383
self.PropertyChanged(dbus.String(u"last_checked_ok"),
384
(_datetime_to_dbus_struct
385
(self.last_checked_ok,
372
388
def start_checker(self):
373
389
"""Start a new checker subprocess if one is not running.
501
497
dbus.String("host"):
502
498
dbus.String(self.host, variant_level=1),
503
499
dbus.String("created"):
504
_datetime_to_dbus(self.created, variant_level=1),
505
dbus.String("last_enabled"):
506
(_datetime_to_dbus(self.last_enabled,
508
if self.last_enabled is not None
500
_datetime_to_dbus_struct(self.created,
502
dbus.String("last_started"):
503
(_datetime_to_dbus_struct(self.last_started,
505
if self.last_started is not None
509
506
else dbus.Boolean(False, variant_level=1)),
510
dbus.String("enabled"):
511
dbus.Boolean(self.enabled, variant_level=1),
507
dbus.String("started"):
508
dbus.Boolean(self.started, variant_level=1),
512
509
dbus.String("last_checked_ok"):
513
(_datetime_to_dbus(self.last_checked_ok,
510
(_datetime_to_dbus_struct(self.last_checked_ok,
515
512
if self.last_checked_ok is not None
516
513
else dbus.Boolean (False, variant_level=1)),
517
514
dbus.String("timeout"):
518
dbus.UInt64(self.timeout_milliseconds(),
515
dbus.UInt64(self._timeout_milliseconds,
519
516
variant_level=1),
520
517
dbus.String("interval"):
521
dbus.UInt64(self.interval_milliseconds(),
518
dbus.UInt64(self._interval_milliseconds,
522
519
variant_level=1),
523
520
dbus.String("checker"):
524
521
dbus.String(self.checker_command,
547
541
def SetChecker(self, checker):
548
542
"D-Bus setter method"
549
543
self.checker_command = checker
551
self.PropertyChanged(dbus.String(u"checker"),
552
dbus.String(self.checker_command,
555
545
# SetHost - method
556
546
@dbus.service.method(_interface, in_signature="s")
557
547
def SetHost(self, host):
558
548
"D-Bus setter method"
561
self.PropertyChanged(dbus.String(u"host"),
562
dbus.String(self.host, variant_level=1))
564
551
# SetInterval - method
565
552
@dbus.service.method(_interface, in_signature="t")
566
553
def SetInterval(self, milliseconds):
567
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
569
self.PropertyChanged(dbus.String(u"interval"),
570
(dbus.UInt64(self.interval_milliseconds(),
554
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
573
556
# SetSecret - method
574
557
@dbus.service.method(_interface, in_signature="ay",
978
937
server_config.read(os.path.join(options.configdir, "mandos.conf"))
979
938
# Convert the SafeConfigParser object to a dict
980
939
server_settings = server_config.defaults()
981
# Use the appropriate methods on the non-string config options
982
server_settings["debug"] = server_config.getboolean("DEFAULT",
984
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
986
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
988
if server_settings["port"]:
989
server_settings["port"] = server_config.getint("DEFAULT",
940
# Use getboolean on the boolean config option
941
server_settings["debug"] = (server_config.getboolean
942
("DEFAULT", "debug"))
991
943
del server_config
993
945
# Override the settings from the config file with command line
994
946
# options, if set.
995
947
for option in ("interface", "address", "port", "debug",
996
"priority", "servicename", "configdir",
997
"use_dbus", "use_ipv6"):
948
"priority", "servicename", "configdir"):
998
949
value = getattr(options, option)
999
950
if value is not None:
1000
951
server_settings[option] = value
1002
953
# Now we have our good server settings in "server_settings"
1005
955
debug = server_settings["debug"]
1006
use_dbus = server_settings["use_dbus"]
1007
use_ipv6 = server_settings["use_ipv6"]
1010
958
syslogger.setLevel(logging.WARNING)
1031
979
server_settings["port"]),
1033
981
settings=server_settings,
1034
clients=clients, use_ipv6=use_ipv6)
1035
983
pidfilename = "/var/run/mandos.pid"
1037
985
pidfile = open(pidfilename, "w")
986
except IOError, error:
1039
987
logger.error("Could not open file %r", pidfilename)
1042
990
uid = pwd.getpwnam("_mandos").pw_uid
993
uid = pwd.getpwnam("mandos").pw_uid
996
uid = pwd.getpwnam("nobody").pw_uid
1043
1000
gid = pwd.getpwnam("_mandos").pw_gid
1044
1001
except KeyError:
1046
uid = pwd.getpwnam("mandos").pw_uid
1047
1003
gid = pwd.getpwnam("mandos").pw_gid
1048
1004
except KeyError:
1050
uid = pwd.getpwnam("nobody").pw_uid
1051
1006
gid = pwd.getpwnam("nogroup").pw_gid
1052
1007
except KeyError:
1058
1012
except OSError, error:
1059
1013
if error[0] != errno.EPERM:
1062
# Enable all possible GnuTLS debugging
1064
# "Use a log level over 10 to enable all debugging options."
1066
gnutls.library.functions.gnutls_global_set_log_level(11)
1068
@gnutls.library.types.gnutls_log_func
1069
def debug_gnutls(level, string):
1070
logger.debug("GnuTLS: %s", string[:-1])
1072
(gnutls.library.functions
1073
.gnutls_global_set_log_function(debug_gnutls))
1076
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1077
1017
service = AvahiService(name = server_settings["servicename"],
1078
servicetype = "_mandos._tcp",
1079
protocol = protocol)
1018
servicetype = "_mandos._tcp", )
1080
1019
if server_settings["interface"]:
1081
1020
service.interface = (if_nametoindex
1082
1021
(server_settings["interface"]))
1149
1087
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1150
1088
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1153
class MandosServer(dbus.service.Object):
1154
"""A D-Bus proxy object"""
1156
dbus.service.Object.__init__(self, bus, "/")
1157
_interface = u"se.bsnet.fukt.Mandos"
1159
@dbus.service.signal(_interface, signature="oa{sv}")
1160
def ClientAdded(self, objpath, properties):
1164
@dbus.service.signal(_interface, signature="os")
1165
def ClientRemoved(self, objpath, name):
1169
@dbus.service.method(_interface, out_signature="ao")
1170
def GetAllClients(self):
1172
return dbus.Array(c.dbus_object_path for c in clients)
1174
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1175
def GetAllClientsWithProperties(self):
1177
return dbus.Dictionary(
1178
((c.dbus_object_path, c.GetAllProperties())
1182
@dbus.service.method(_interface, in_signature="o")
1183
def RemoveClient(self, object_path):
1186
if c.dbus_object_path == object_path:
1188
# Don't signal anything except ClientRemoved
1192
self.ClientRemoved(object_path, c.name)
1198
mandos_server = MandosServer()
1090
class MandosServer(dbus.service.Object):
1091
"""A D-Bus proxy object"""
1093
dbus.service.Object.__init__(self, bus,
1095
_interface = u"org.mandos_system.Mandos"
1097
@dbus.service.signal(_interface, signature="oa{sv}")
1098
def ClientAdded(self, objpath, properties):
1102
@dbus.service.signal(_interface, signature="o")
1103
def ClientRemoved(self, objpath):
1107
@dbus.service.method(_interface, out_signature="ao")
1108
def GetAllClients(self):
1109
return dbus.Array(c.dbus_object_path for c in clients)
1111
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1112
def GetAllClientsWithProperties(self):
1113
return dbus.Dictionary(
1114
((c.dbus_object_path, c.GetAllProperties())
1118
@dbus.service.method(_interface, in_signature="o")
1119
def RemoveClient(self, object_path):
1121
if c.dbus_object_path == object_path:
1129
mandos_server = MandosServer()
1200
1131
for client in clients:
1203
mandos_server.ClientAdded(client.dbus_object_path,
1204
client.GetAllProperties())
1133
mandos_server.ClientAdded(client.dbus_object_path,
1134
client.GetAllProperties())
1207
1137
tcp_server.enable()
1208
1138
tcp_server.server_activate()
1210
1140
# Find out what port we got
1211
1141
service.port = tcp_server.socket.getsockname()[1]
1213
logger.info(u"Now listening on address %r, port %d,"
1214
" flowinfo %d, scope_id %d"
1215
% tcp_server.socket.getsockname())
1217
logger.info(u"Now listening on address %r, port %d"
1218
% tcp_server.socket.getsockname())
1142
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
1143
u" scope_id %d" % tcp_server.socket.getsockname())
1220
1145
#service.interface = tcp_server.socket.getsockname()[3]