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: %(levelname)s: %(message)s'))
76
('Mandos [%(process)d]: %(levelname)s:'
77
78
logger.addHandler(syslogger)
79
80
console = logging.StreamHandler()
80
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
81
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
82
' %(levelname)s: %(message)s'))
82
83
logger.addHandler(console)
84
85
class AvahiError(Exception):
85
def __init__(self, value):
86
def __init__(self, value, *args, **kwargs):
87
super(AvahiError, self).__init__()
89
return repr(self.value)
88
super(AvahiError, self).__init__(value, *args, **kwargs)
89
def __unicode__(self):
90
return unicode(repr(self.value))
91
92
class AvahiServiceError(AvahiError):
170
171
# End of Avahi example code
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)
174
def _datetime_to_dbus(dt, variant_level=0):
175
"""Convert a UTC datetime.datetime() to a D-Bus type."""
176
return dbus.String(dt.isoformat(), variant_level=variant_level)
188
179
class Client(dbus.service.Object):
189
180
"""A representation of a client host served by this server.
191
name: string; from the config file, used in log messages
182
name: string; from the config file, used in log messages and
192
184
fingerprint: string (40 or 32 hexadecimal digits); used to
193
185
uniquely identify the client
194
186
secret: bytestring; sent verbatim (over TLS) to client
195
187
host: string; available for use by the checker command
196
188
created: datetime.datetime(); (UTC) object creation
197
last_started: datetime.datetime(); (UTC)
189
last_enabled: datetime.datetime(); (UTC)
199
191
last_checked_ok: datetime.datetime(); (UTC) or None
200
192
timeout: datetime.timedelta(); How long from last_checked_ok
201
193
until this client is invalid
202
194
interval: datetime.timedelta(); How often to start a new checker
203
stop_hook: If set, called by stop() as stop_hook(self)
195
disable_hook: If set, called by disable() as disable_hook(self)
204
196
checker: subprocess.Popen(); a running checker process used
205
197
to see if the client lives.
206
198
'None' if no process is running.
207
199
checker_initiator_tag: a gobject event source tag, or None
208
stop_initiator_tag: - '' -
200
disable_initiator_tag: - '' -
209
201
checker_callback_tag: - '' -
210
202
checker_command: string; External command which is run to check if
211
203
client lives. %() expansions are done at
212
204
runtime with vars(self) as dict, so that for
213
205
instance %(name)s can be used in the command.
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: - '' -
206
use_dbus: bool(); Whether to provide D-Bus interface and signals
207
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
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):
209
def timeout_milliseconds(self):
210
"Return the 'timeout' attribute in milliseconds"
211
return ((self.timeout.days * 24 * 60 * 60 * 1000)
212
+ (self.timeout.seconds * 1000)
213
+ (self.timeout.microseconds // 1000))
215
def interval_milliseconds(self):
216
"Return the 'interval' attribute in milliseconds"
217
return ((self.interval.days * 24 * 60 * 60 * 1000)
218
+ (self.interval.seconds * 1000)
219
+ (self.interval.microseconds // 1000))
221
def __init__(self, name = None, disable_hook=None, config=None,
253
223
"""Note: the 'checker' key in 'config' sets the
254
224
'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)
261
227
if config is None:
264
229
logger.debug(u"Creating client %r", self.name)
230
self.use_dbus = False # During __init__
265
231
# Uppercase and remove spaces from fingerprint for later
266
232
# comparison purposes with return value from the fingerprint()
281
247
self.host = config.get("host", "")
282
248
self.created = datetime.datetime.utcnow()
284
self.last_started = None
250
self.last_enabled = None
285
251
self.last_checked_ok = None
286
252
self.timeout = string_to_delta(config["timeout"])
287
253
self.interval = string_to_delta(config["interval"])
288
self.stop_hook = stop_hook
254
self.disable_hook = disable_hook
289
255
self.checker = None
290
256
self.checker_initiator_tag = None
291
self.stop_initiator_tag = None
257
self.disable_initiator_tag = None
292
258
self.checker_callback_tag = None
293
259
self.checker_command = config["checker"]
260
self.last_connect = None
261
# Only now, when this client is initialized, can it show up on
263
self.use_dbus = use_dbus
265
self.dbus_object_path = (dbus.ObjectPath
267
+ self.name.replace(".", "_")))
268
dbus.service.Object.__init__(self, bus,
269
self.dbus_object_path)
296
272
"""Start this client's checker and timeout hooks"""
297
self.last_started = datetime.datetime.utcnow()
273
self.last_enabled = datetime.datetime.utcnow()
298
274
# Schedule a new checker to be started an 'interval' from now,
299
275
# and every interval from then on.
300
276
self.checker_initiator_tag = (gobject.timeout_add
301
(self._interval_milliseconds,
277
(self.interval_milliseconds(),
302
278
self.start_checker))
303
279
# Also start a new checker *right now*.
304
280
self.start_checker()
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)))
281
# Schedule a disable() when 'timeout' has passed
282
self.disable_initiator_tag = (gobject.timeout_add
283
(self.timeout_milliseconds(),
288
self.PropertyChanged(dbus.String(u"enabled"),
289
dbus.Boolean(True, variant_level=1))
290
self.PropertyChanged(dbus.String(u"last_enabled"),
291
(_datetime_to_dbus(self.last_enabled,
318
"""Stop this client."""
319
if not getattr(self, "started", False):
295
"""Disable this client."""
296
if not getattr(self, "enabled", False):
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
298
logger.info(u"Disabling client %s", self.name)
299
if getattr(self, "disable_initiator_tag", False):
300
gobject.source_remove(self.disable_initiator_tag)
301
self.disable_initiator_tag = None
325
302
if getattr(self, "checker_initiator_tag", False):
326
303
gobject.source_remove(self.checker_initiator_tag)
327
304
self.checker_initiator_tag = None
328
305
self.stop_checker()
333
self.PropertyChanged(dbus.String(u"started"),
334
dbus.Boolean(False, variant_level=1))
306
if self.disable_hook:
307
self.disable_hook(self)
311
self.PropertyChanged(dbus.String(u"enabled"),
312
dbus.Boolean(False, variant_level=1))
335
313
# Do not run this again if called by a gobject.timeout_add
338
316
def __del__(self):
339
self.stop_hook = None
317
self.disable_hook = None
342
320
def checker_callback(self, pid, condition, command):
343
321
"""The checker has completed, so take appropriate actions."""
344
322
self.checker_callback_tag = None
345
323
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",
353
325
# Emit D-Bus signal
354
self.CheckerCompleted(dbus.Boolean(True),
355
dbus.UInt16(condition),
356
dbus.String(command))
358
elif not os.WIFEXITED(condition):
326
self.PropertyChanged(dbus.String(u"checker_running"),
327
dbus.Boolean(False, variant_level=1))
328
if os.WIFEXITED(condition):
329
exitstatus = os.WEXITSTATUS(condition)
331
logger.info(u"Checker for %(name)s succeeded",
335
logger.info(u"Checker for %(name)s failed",
339
self.CheckerCompleted(dbus.Int16(exitstatus),
340
dbus.Int64(condition),
341
dbus.String(command))
359
343
logger.warning(u"Checker for %(name)s crashed?",
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))
347
self.CheckerCompleted(dbus.Int16(-1),
348
dbus.Int64(condition),
349
dbus.String(command))
373
def bump_timeout(self):
351
def checked_ok(self):
374
352
"""Bump up the timeout for this client.
375
353
This should only be called when the client has been seen,
378
356
self.last_checked_ok = datetime.datetime.utcnow()
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,
357
gobject.source_remove(self.disable_initiator_tag)
358
self.disable_initiator_tag = (gobject.timeout_add
359
(self.timeout_milliseconds(),
363
self.PropertyChanged(
364
dbus.String(u"last_checked_ok"),
365
(_datetime_to_dbus(self.last_checked_ok,
388
368
def start_checker(self):
389
369
"""Start a new checker subprocess if one is not running.
497
480
dbus.String("host"):
498
481
dbus.String(self.host, variant_level=1),
499
482
dbus.String("created"):
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
483
_datetime_to_dbus(self.created, variant_level=1),
484
dbus.String("last_enabled"):
485
(_datetime_to_dbus(self.last_enabled,
487
if self.last_enabled is not None
506
488
else dbus.Boolean(False, variant_level=1)),
507
dbus.String("started"):
508
dbus.Boolean(self.started, variant_level=1),
489
dbus.String("enabled"):
490
dbus.Boolean(self.enabled, variant_level=1),
509
491
dbus.String("last_checked_ok"):
510
(_datetime_to_dbus_struct(self.last_checked_ok,
492
(_datetime_to_dbus(self.last_checked_ok,
512
494
if self.last_checked_ok is not None
513
495
else dbus.Boolean (False, variant_level=1)),
514
496
dbus.String("timeout"):
515
dbus.UInt64(self._timeout_milliseconds,
497
dbus.UInt64(self.timeout_milliseconds(),
516
498
variant_level=1),
517
499
dbus.String("interval"):
518
dbus.UInt64(self._interval_milliseconds,
500
dbus.UInt64(self.interval_milliseconds(),
519
501
variant_level=1),
520
502
dbus.String("checker"):
521
503
dbus.String(self.checker_command,
541
526
def SetChecker(self, checker):
542
527
"D-Bus setter method"
543
528
self.checker_command = checker
530
self.PropertyChanged(dbus.String(u"checker"),
531
dbus.String(self.checker_command,
545
534
# SetHost - method
546
535
@dbus.service.method(_interface, in_signature="s")
547
536
def SetHost(self, host):
548
537
"D-Bus setter method"
540
self.PropertyChanged(dbus.String(u"host"),
541
dbus.String(self.host, variant_level=1))
551
543
# SetInterval - method
552
544
@dbus.service.method(_interface, in_signature="t")
553
545
def SetInterval(self, milliseconds):
554
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
546
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
548
self.PropertyChanged(dbus.String(u"interval"),
549
(dbus.UInt64(self.interval_milliseconds(),
556
552
# SetSecret - method
557
553
@dbus.service.method(_interface, in_signature="ay",
937
947
server_config.read(os.path.join(options.configdir, "mandos.conf"))
938
948
# Convert the SafeConfigParser object to a dict
939
949
server_settings = server_config.defaults()
940
# Use getboolean on the boolean config option
941
server_settings["debug"] = (server_config.getboolean
942
("DEFAULT", "debug"))
950
# Use the appropriate methods on the non-string config options
951
server_settings["debug"] = server_config.getboolean("DEFAULT",
953
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
955
if server_settings["port"]:
956
server_settings["port"] = server_config.getint("DEFAULT",
943
958
del server_config
945
960
# Override the settings from the config file with command line
946
961
# options, if set.
947
962
for option in ("interface", "address", "port", "debug",
948
"priority", "servicename", "configdir"):
963
"priority", "servicename", "configdir",
949
965
value = getattr(options, option)
950
966
if value is not None:
951
967
server_settings[option] = value
953
969
# Now we have our good server settings in "server_settings"
955
972
debug = server_settings["debug"]
973
use_dbus = server_settings["use_dbus"]
958
976
syslogger.setLevel(logging.WARNING)
983
1001
pidfilename = "/var/run/mandos.pid"
985
1003
pidfile = open(pidfilename, "w")
986
except IOError, error:
987
1005
logger.error("Could not open file %r", pidfilename)
990
1008
uid = pwd.getpwnam("_mandos").pw_uid
1009
gid = pwd.getpwnam("_mandos").pw_gid
991
1010
except KeyError:
993
1012
uid = pwd.getpwnam("mandos").pw_uid
1013
gid = pwd.getpwnam("mandos").pw_gid
994
1014
except KeyError:
996
1016
uid = pwd.getpwnam("nobody").pw_uid
1017
gid = pwd.getpwnam("nogroup").pw_gid
997
1018
except KeyError:
1000
gid = pwd.getpwnam("_mandos").pw_gid
1003
gid = pwd.getpwnam("mandos").pw_gid
1006
gid = pwd.getpwnam("nogroup").pw_gid
1012
1024
except OSError, error:
1013
1025
if error[0] != errno.EPERM:
1028
# Enable all possible GnuTLS debugging
1030
# "Use a log level over 10 to enable all debugging options."
1032
gnutls.library.functions.gnutls_global_set_log_level(11)
1034
@gnutls.library.types.gnutls_log_func
1035
def debug_gnutls(level, string):
1036
logger.debug("GnuTLS: %s", string[:-1])
1038
(gnutls.library.functions
1039
.gnutls_global_set_log_function(debug_gnutls))
1017
1042
service = AvahiService(name = server_settings["servicename"],
1018
1043
servicetype = "_mandos._tcp", )
1087
1113
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1088
1114
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
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()
1117
class MandosServer(dbus.service.Object):
1118
"""A D-Bus proxy object"""
1120
dbus.service.Object.__init__(self, bus, "/")
1121
_interface = u"se.bsnet.fukt.Mandos"
1123
@dbus.service.signal(_interface, signature="oa{sv}")
1124
def ClientAdded(self, objpath, properties):
1128
@dbus.service.signal(_interface, signature="os")
1129
def ClientRemoved(self, objpath, name):
1133
@dbus.service.method(_interface, out_signature="ao")
1134
def GetAllClients(self):
1136
return dbus.Array(c.dbus_object_path for c in clients)
1138
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1139
def GetAllClientsWithProperties(self):
1141
return dbus.Dictionary(
1142
((c.dbus_object_path, c.GetAllProperties())
1146
@dbus.service.method(_interface, in_signature="o")
1147
def RemoveClient(self, object_path):
1150
if c.dbus_object_path == object_path:
1152
# Don't signal anything except ClientRemoved
1156
self.ClientRemoved(object_path, c.name)
1162
mandos_server = MandosServer()
1131
1164
for client in clients:
1133
mandos_server.ClientAdded(client.dbus_object_path,
1134
client.GetAllProperties())
1167
mandos_server.ClientAdded(client.dbus_object_path,
1168
client.GetAllProperties())
1137
1171
tcp_server.enable()
1138
1172
tcp_server.server_activate()