170
170
# 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)
173
def _datetime_to_dbus(dt, variant_level=0):
174
"""Convert a UTC datetime.datetime() to a D-Bus type."""
175
return dbus.String(dt.isoformat(), variant_level=variant_level)
188
178
class Client(dbus.service.Object):
189
179
"""A representation of a client host served by this server.
191
name: string; from the config file, used in log messages
181
name: string; from the config file, used in log messages and
192
183
fingerprint: string (40 or 32 hexadecimal digits); used to
193
184
uniquely identify the client
194
185
secret: bytestring; sent verbatim (over TLS) to client
195
186
host: string; available for use by the checker command
196
187
created: datetime.datetime(); (UTC) object creation
197
last_started: datetime.datetime(); (UTC)
188
last_enabled: datetime.datetime(); (UTC)
199
190
last_checked_ok: datetime.datetime(); (UTC) or None
200
191
timeout: datetime.timedelta(); How long from last_checked_ok
201
192
until this client is invalid
202
193
interval: datetime.timedelta(); How often to start a new checker
203
stop_hook: If set, called by stop() as stop_hook(self)
194
disable_hook: If set, called by disable() as disable_hook(self)
204
195
checker: subprocess.Popen(); a running checker process used
205
196
to see if the client lives.
206
197
'None' if no process is running.
207
198
checker_initiator_tag: a gobject event source tag, or None
208
stop_initiator_tag: - '' -
199
disable_initiator_tag: - '' -
209
200
checker_callback_tag: - '' -
210
201
checker_command: string; External command which is run to check if
211
202
client lives. %() expansions are done at
212
203
runtime with vars(self) as dict, so that for
213
204
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: - '' -
205
use_dbus: bool(); Whether to provide D-Bus interface and signals
206
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):
208
def timeout_milliseconds(self):
209
"Return the 'timeout' attribute in milliseconds"
210
return ((self.timeout.days * 24 * 60 * 60 * 1000)
211
+ (self.timeout.seconds * 1000)
212
+ (self.timeout.microseconds // 1000))
214
def interval_milliseconds(self):
215
"Return the 'interval' attribute in milliseconds"
216
return ((self.interval.days * 24 * 60 * 60 * 1000)
217
+ (self.interval.seconds * 1000)
218
+ (self.interval.microseconds // 1000))
220
def __init__(self, name = None, disable_hook=None, config=None,
253
222
"""Note: the 'checker' key in 'config' sets the
254
223
'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
226
if config is None:
264
228
logger.debug(u"Creating client %r", self.name)
229
self.use_dbus = False # During __init__
265
230
# Uppercase and remove spaces from fingerprint for later
266
231
# comparison purposes with return value from the fingerprint()
281
246
self.host = config.get("host", "")
282
247
self.created = datetime.datetime.utcnow()
284
self.last_started = None
249
self.last_enabled = None
285
250
self.last_checked_ok = None
286
251
self.timeout = string_to_delta(config["timeout"])
287
252
self.interval = string_to_delta(config["interval"])
288
self.stop_hook = stop_hook
253
self.disable_hook = disable_hook
289
254
self.checker = None
290
255
self.checker_initiator_tag = None
291
self.stop_initiator_tag = None
256
self.disable_initiator_tag = None
292
257
self.checker_callback_tag = None
293
258
self.checker_command = config["checker"]
259
self.last_connect = None
260
# Only now, when this client is initialized, can it show up on
262
self.use_dbus = use_dbus
264
self.dbus_object_path = (dbus.ObjectPath
266
+ self.name.replace(".", "_")))
267
dbus.service.Object.__init__(self, bus,
268
self.dbus_object_path)
296
271
"""Start this client's checker and timeout hooks"""
297
self.last_started = datetime.datetime.utcnow()
272
self.last_enabled = datetime.datetime.utcnow()
298
273
# Schedule a new checker to be started an 'interval' from now,
299
274
# and every interval from then on.
300
275
self.checker_initiator_tag = (gobject.timeout_add
301
(self._interval_milliseconds,
276
(self.interval_milliseconds(),
302
277
self.start_checker))
303
278
# Also start a new checker *right now*.
304
279
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)))
280
# Schedule a disable() when 'timeout' has passed
281
self.disable_initiator_tag = (gobject.timeout_add
282
(self.timeout_milliseconds(),
287
self.PropertyChanged(dbus.String(u"enabled"),
288
dbus.Boolean(True, variant_level=1))
289
self.PropertyChanged(dbus.String(u"last_enabled"),
290
(_datetime_to_dbus(self.last_enabled,
318
"""Stop this client."""
319
if not getattr(self, "started", False):
294
"""Disable this client."""
295
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
297
logger.info(u"Disabling client %s", self.name)
298
if getattr(self, "disable_initiator_tag", False):
299
gobject.source_remove(self.disable_initiator_tag)
300
self.disable_initiator_tag = None
325
301
if getattr(self, "checker_initiator_tag", False):
326
302
gobject.source_remove(self.checker_initiator_tag)
327
303
self.checker_initiator_tag = None
328
304
self.stop_checker()
333
self.PropertyChanged(dbus.String(u"started"),
334
dbus.Boolean(False, variant_level=1))
305
if self.disable_hook:
306
self.disable_hook(self)
310
self.PropertyChanged(dbus.String(u"enabled"),
311
dbus.Boolean(False, variant_level=1))
335
312
# Do not run this again if called by a gobject.timeout_add
338
315
def __del__(self):
339
self.stop_hook = None
316
self.disable_hook = None
342
319
def checker_callback(self, pid, condition, command):
343
320
"""The checker has completed, so take appropriate actions."""
344
321
self.checker_callback_tag = None
345
322
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
324
# Emit D-Bus signal
354
self.CheckerCompleted(dbus.Boolean(True),
355
dbus.UInt16(condition),
356
dbus.String(command))
358
elif not os.WIFEXITED(condition):
325
self.PropertyChanged(dbus.String(u"checker_running"),
326
dbus.Boolean(False, variant_level=1))
327
if os.WIFEXITED(condition):
328
exitstatus = os.WEXITSTATUS(condition)
330
logger.info(u"Checker for %(name)s succeeded",
334
logger.info(u"Checker for %(name)s failed",
338
self.CheckerCompleted(dbus.Int16(exitstatus),
339
dbus.Int64(condition),
340
dbus.String(command))
359
342
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))
346
self.CheckerCompleted(dbus.Int16(-1),
347
dbus.Int64(condition),
348
dbus.String(command))
373
def bump_timeout(self):
350
def checked_ok(self):
374
351
"""Bump up the timeout for this client.
375
352
This should only be called when the client has been seen,
378
355
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,
356
gobject.source_remove(self.disable_initiator_tag)
357
self.disable_initiator_tag = (gobject.timeout_add
358
(self.timeout_milliseconds(),
362
self.PropertyChanged(
363
dbus.String(u"last_checked_ok"),
364
(_datetime_to_dbus(self.last_checked_ok,
388
367
def start_checker(self):
389
368
"""Start a new checker subprocess if one is not running.
497
479
dbus.String("host"):
498
480
dbus.String(self.host, variant_level=1),
499
481
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
482
_datetime_to_dbus(self.created, variant_level=1),
483
dbus.String("last_enabled"):
484
(_datetime_to_dbus(self.last_enabled,
486
if self.last_enabled is not None
506
487
else dbus.Boolean(False, variant_level=1)),
507
dbus.String("started"):
508
dbus.Boolean(self.started, variant_level=1),
488
dbus.String("enabled"):
489
dbus.Boolean(self.enabled, variant_level=1),
509
490
dbus.String("last_checked_ok"):
510
(_datetime_to_dbus_struct(self.last_checked_ok,
491
(_datetime_to_dbus(self.last_checked_ok,
512
493
if self.last_checked_ok is not None
513
494
else dbus.Boolean (False, variant_level=1)),
514
495
dbus.String("timeout"):
515
dbus.UInt64(self._timeout_milliseconds,
496
dbus.UInt64(self.timeout_milliseconds(),
516
497
variant_level=1),
517
498
dbus.String("interval"):
518
dbus.UInt64(self._interval_milliseconds,
499
dbus.UInt64(self.interval_milliseconds(),
519
500
variant_level=1),
520
501
dbus.String("checker"):
521
502
dbus.String(self.checker_command,
541
525
def SetChecker(self, checker):
542
526
"D-Bus setter method"
543
527
self.checker_command = checker
529
self.PropertyChanged(dbus.String(u"checker"),
530
dbus.String(self.checker_command,
545
533
# SetHost - method
546
534
@dbus.service.method(_interface, in_signature="s")
547
535
def SetHost(self, host):
548
536
"D-Bus setter method"
539
self.PropertyChanged(dbus.String(u"host"),
540
dbus.String(self.host, variant_level=1))
551
542
# SetInterval - method
552
543
@dbus.service.method(_interface, in_signature="t")
553
544
def SetInterval(self, milliseconds):
554
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
545
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
547
self.PropertyChanged(dbus.String(u"interval"),
548
(dbus.UInt64(self.interval_milliseconds(),
556
551
# SetSecret - method
557
552
@dbus.service.method(_interface, in_signature="ay",
937
946
server_config.read(os.path.join(options.configdir, "mandos.conf"))
938
947
# Convert the SafeConfigParser object to a dict
939
948
server_settings = server_config.defaults()
940
# Use getboolean on the boolean config option
941
server_settings["debug"] = (server_config.getboolean
942
("DEFAULT", "debug"))
949
# Use the appropriate methods on the non-string config options
950
server_settings["debug"] = server_config.getboolean("DEFAULT",
952
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
954
if server_settings["port"]:
955
server_settings["port"] = server_config.getint("DEFAULT",
943
957
del server_config
945
959
# Override the settings from the config file with command line
946
960
# options, if set.
947
961
for option in ("interface", "address", "port", "debug",
948
"priority", "servicename", "configdir"):
962
"priority", "servicename", "configdir",
949
964
value = getattr(options, option)
950
965
if value is not None:
951
966
server_settings[option] = value
953
968
# Now we have our good server settings in "server_settings"
955
971
debug = server_settings["debug"]
972
use_dbus = server_settings["use_dbus"]
974
def sigsegvhandler(signum, frame):
975
raise RuntimeError('Segmentation fault')
958
978
syslogger.setLevel(logging.WARNING)
959
979
console.setLevel(logging.WARNING)
981
signal.signal(signal.SIGSEGV, sigsegvhandler)
961
983
if server_settings["servicename"] != "Mandos":
962
984
syslogger.setFormatter(logging.Formatter
990
1012
uid = pwd.getpwnam("_mandos").pw_uid
1013
gid = pwd.getpwnam("_mandos").pw_gid
991
1014
except KeyError:
993
1016
uid = pwd.getpwnam("mandos").pw_uid
1017
gid = pwd.getpwnam("mandos").pw_gid
994
1018
except KeyError:
996
1020
uid = pwd.getpwnam("nobody").pw_uid
1021
gid = pwd.getpwnam("nogroup").pw_gid
997
1022
except KeyError:
1000
gid = pwd.getpwnam("_mandos").pw_gid
1003
gid = pwd.getpwnam("mandos").pw_gid
1006
gid = pwd.getpwnam("nogroup").pw_gid
1012
1028
except OSError, error:
1013
1029
if error[0] != errno.EPERM:
1032
# Enable all possible GnuTLS debugging
1034
# "Use a log level over 10 to enable all debugging options."
1036
gnutls.library.functions.gnutls_global_set_log_level(11)
1038
@gnutls.library.types.gnutls_log_func
1039
def debug_gnutls(level, string):
1040
logger.debug("GnuTLS: %s", string[:-1])
1042
(gnutls.library.functions
1043
.gnutls_global_set_log_function(debug_gnutls))
1017
1046
service = AvahiService(name = server_settings["servicename"],
1018
1047
servicetype = "_mandos._tcp", )
1087
1117
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1088
1118
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()
1121
class MandosServer(dbus.service.Object):
1122
"""A D-Bus proxy object"""
1124
dbus.service.Object.__init__(self, bus, "/")
1125
_interface = u"se.bsnet.fukt.Mandos"
1127
@dbus.service.signal(_interface, signature="oa{sv}")
1128
def ClientAdded(self, objpath, properties):
1132
@dbus.service.signal(_interface, signature="os")
1133
def ClientRemoved(self, objpath, name):
1137
@dbus.service.method(_interface, out_signature="ao")
1138
def GetAllClients(self):
1140
return dbus.Array(c.dbus_object_path for c in clients)
1142
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1143
def GetAllClientsWithProperties(self):
1145
return dbus.Dictionary(
1146
((c.dbus_object_path, c.GetAllProperties())
1150
@dbus.service.method(_interface, in_signature="o")
1151
def RemoveClient(self, object_path):
1154
if c.dbus_object_path == object_path:
1156
# Don't signal anything except ClientRemoved
1160
self.ClientRemoved(object_path, c.name)
1166
mandos_server = MandosServer()
1131
1168
for client in clients:
1133
mandos_server.ClientAdded(client.dbus_object_path,
1134
client.GetAllProperties())
1171
mandos_server.ClientAdded(client.dbus_object_path,
1172
client.GetAllProperties())
1137
1175
tcp_server.enable()
1138
1176
tcp_server.server_activate()