352
186
dbus.UInt16(self.port),
353
187
avahi.string_array_to_txt_array(self.TXT))
354
188
self.group.Commit()
356
189
def entry_group_state_changed(self, state, error):
357
190
"""Derived from the Avahi example code"""
358
logger.debug("Avahi entry group state change: %i", state)
191
logger.debug(u"Avahi state change: %i", state)
360
193
if state == avahi.ENTRY_GROUP_ESTABLISHED:
361
logger.debug("Zeroconf service established.")
194
logger.debug(u"Zeroconf service established.")
362
195
elif state == avahi.ENTRY_GROUP_COLLISION:
363
logger.info("Zeroconf service name collision.")
196
logger.warning(u"Zeroconf service name collision.")
365
198
elif state == avahi.ENTRY_GROUP_FAILURE:
366
logger.critical("Avahi: Error in group state changed %s",
368
raise AvahiGroupError("State changed: {!s}".format(error))
199
logger.critical(u"Avahi: Error in group state changed %s",
201
raise AvahiGroupError(u"State changed: %s"
370
203
def cleanup(self):
371
204
"""Derived from the Avahi example code"""
372
205
if self.group is not None:
375
except (dbus.exceptions.UnknownMethodException,
376
dbus.exceptions.DBusException):
378
207
self.group = None
381
def server_state_changed(self, state, error=None):
208
def server_state_changed(self, state):
382
209
"""Derived from the Avahi example code"""
383
logger.debug("Avahi server state change: %i", state)
385
avahi.SERVER_INVALID: "Zeroconf server invalid",
386
avahi.SERVER_REGISTERING: None,
387
avahi.SERVER_COLLISION: "Zeroconf server name collision",
388
avahi.SERVER_FAILURE: "Zeroconf server failure",
390
if state in bad_states:
391
if bad_states[state] is not None:
393
logger.error(bad_states[state])
395
logger.error(bad_states[state] + ": %r", error)
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
397
213
elif state == avahi.SERVER_RUNNING:
401
logger.debug("Unknown state: %r", state)
403
logger.debug("Unknown state: %r: %r", state, error)
405
215
def activate(self):
406
216
"""Derived from the Avahi example code"""
407
217
if self.server is None:
408
218
self.server = dbus.Interface(
409
219
self.bus.get_object(avahi.DBUS_NAME,
410
avahi.DBUS_PATH_SERVER,
411
follow_name_owner_changes=True),
220
avahi.DBUS_PATH_SERVER),
412
221
avahi.DBUS_INTERFACE_SERVER)
413
self.server.connect_to_signal("StateChanged",
414
self.server_state_changed)
222
self.server.connect_to_signal(u"StateChanged",
223
self.server_state_changed)
415
224
self.server_state_changed(self.server.GetState())
418
class AvahiServiceToSyslog(AvahiService):
419
def rename(self, *args, **kwargs):
420
"""Add the new name to the syslog messages"""
421
ret = AvahiService.rename(self, *args, **kwargs)
422
syslogger.setFormatter(logging.Formatter(
423
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
428
227
class Client(object):
429
228
"""A representation of a client host served by this server.
432
approved: bool(); 'None' if not yet approved/disapproved
433
approval_delay: datetime.timedelta(); Time to wait for approval
434
approval_duration: datetime.timedelta(); Duration of one approval
231
name: string; from the config file, used in log messages and
233
fingerprint: string (40 or 32 hexadecimal digits); used to
234
uniquely identify the client
235
secret: bytestring; sent verbatim (over TLS) to client
236
host: string; available for use by the checker command
237
created: datetime.datetime(); (UTC) object creation
238
last_enabled: datetime.datetime(); (UTC)
240
last_checked_ok: datetime.datetime(); (UTC) or None
241
timeout: datetime.timedelta(); How long from last_checked_ok
242
until this client is invalid
243
interval: datetime.timedelta(); How often to start a new checker
244
disable_hook: If set, called by disable() as disable_hook(self)
435
245
checker: subprocess.Popen(); a running checker process used
436
246
to see if the client lives.
437
247
'None' if no process is running.
438
checker_callback_tag: a gobject event source tag, or None
439
checker_command: string; External command which is run to check
440
if client lives. %() expansions are done at
248
checker_initiator_tag: a gobject event source tag, or None
249
disable_initiator_tag: - '' -
250
checker_callback_tag: - '' -
251
checker_command: string; External command which is run to check if
252
client lives. %() expansions are done at
441
253
runtime with vars(self) as dict, so that for
442
254
instance %(name)s can be used in the command.
443
checker_initiator_tag: a gobject event source tag, or None
444
created: datetime.datetime(); (UTC) object creation
445
client_structure: Object describing what attributes a client has
446
and is used for storing the client at exit
447
255
current_checker_command: string; current running checker_command
448
disable_initiator_tag: a gobject event source tag, or None
450
fingerprint: string (40 or 32 hexadecimal digits); used to
451
uniquely identify the client
452
host: string; available for use by the checker command
453
interval: datetime.timedelta(); How often to start a new checker
454
last_approval_request: datetime.datetime(); (UTC) or None
455
last_checked_ok: datetime.datetime(); (UTC) or None
456
last_checker_status: integer between 0 and 255 reflecting exit
457
status of last checker. -1 reflects crashed
458
checker, -2 means no checker completed yet.
459
last_enabled: datetime.datetime(); (UTC) or None
460
name: string; from the config file, used in log messages and
462
secret: bytestring; sent verbatim (over TLS) to client
463
timeout: datetime.timedelta(); How long from last_checked_ok
464
until this client is disabled
465
extended_timeout: extra long timeout when secret has been sent
466
runtime_expansions: Allowed attributes for runtime expansion.
467
expires: datetime.datetime(); time (UTC) when a client will be
469
server_settings: The server_settings dict from main()
472
runtime_expansions = ("approval_delay", "approval_duration",
473
"created", "enabled", "expires",
474
"fingerprint", "host", "interval",
475
"last_approval_request", "last_checked_ok",
476
"last_enabled", "name", "timeout")
479
"extended_timeout": "PT15M",
481
"checker": "fping -q -- %%(host)s",
483
"approval_delay": "PT0S",
484
"approval_duration": "PT1S",
485
"approved_by_default": "True",
490
def config_parser(config):
491
"""Construct a new dict of client settings of this form:
492
{ client_name: {setting_name: value, ...}, ...}
493
with exceptions for any special settings as defined above.
494
NOTE: Must be a pure function. Must return the same result
495
value given the same arguments.
498
for client_name in config.sections():
499
section = dict(config.items(client_name))
500
client = settings[client_name] = {}
502
client["host"] = section["host"]
503
# Reformat values from string types to Python types
504
client["approved_by_default"] = config.getboolean(
505
client_name, "approved_by_default")
506
client["enabled"] = config.getboolean(client_name,
509
# Uppercase and remove spaces from fingerprint for later
510
# comparison purposes with return value from the
511
# fingerprint() function
512
client["fingerprint"] = (section["fingerprint"].upper()
514
if "secret" in section:
515
client["secret"] = section["secret"].decode("base64")
516
elif "secfile" in section:
517
with open(os.path.expanduser(os.path.expandvars
518
(section["secfile"])),
520
client["secret"] = secfile.read()
522
raise TypeError("No secret or secfile for section {}"
524
client["timeout"] = string_to_delta(section["timeout"])
525
client["extended_timeout"] = string_to_delta(
526
section["extended_timeout"])
527
client["interval"] = string_to_delta(section["interval"])
528
client["approval_delay"] = string_to_delta(
529
section["approval_delay"])
530
client["approval_duration"] = string_to_delta(
531
section["approval_duration"])
532
client["checker_command"] = section["checker"]
533
client["last_approval_request"] = None
534
client["last_checked_ok"] = None
535
client["last_checker_status"] = -2
539
def __init__(self, settings, name = None, server_settings=None):
259
def _datetime_to_milliseconds(dt):
260
"Convert a datetime.datetime() to milliseconds"
261
return ((dt.days * 24 * 60 * 60 * 1000)
262
+ (dt.seconds * 1000)
263
+ (dt.microseconds // 1000))
265
def timeout_milliseconds(self):
266
"Return the 'timeout' attribute in milliseconds"
267
return self._datetime_to_milliseconds(self.timeout)
269
def interval_milliseconds(self):
270
"Return the 'interval' attribute in milliseconds"
271
return self._datetime_to_milliseconds(self.interval)
273
def __init__(self, name = None, disable_hook=None, config=None):
274
"""Note: the 'checker' key in 'config' sets the
275
'checker_command' attribute and *not* the 'checker'
541
if server_settings is None:
543
self.server_settings = server_settings
544
# adding all client settings
545
for setting, value in settings.items():
546
setattr(self, setting, value)
549
if not hasattr(self, "last_enabled"):
550
self.last_enabled = datetime.datetime.utcnow()
551
if not hasattr(self, "expires"):
552
self.expires = (datetime.datetime.utcnow()
280
logger.debug(u"Creating client %r", self.name)
281
# Uppercase and remove spaces from fingerprint for later
282
# comparison purposes with return value from the fingerprint()
284
self.fingerprint = (config[u"fingerprint"].upper()
286
logger.debug(u" Fingerprint: %s", self.fingerprint)
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
290
with closing(open(os.path.expanduser
292
(config[u"secfile"])))) as secfile:
293
self.secret = secfile.read()
555
self.last_enabled = None
558
logger.debug("Creating client %r", self.name)
559
logger.debug(" Fingerprint: %s", self.fingerprint)
560
self.created = settings.get("created",
561
datetime.datetime.utcnow())
563
# attributes specific for this server instance
295
raise TypeError(u"No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
298
self.created = datetime.datetime.utcnow()
300
self.last_enabled = None
301
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
304
self.disable_hook = disable_hook
564
305
self.checker = None
565
306
self.checker_initiator_tag = None
566
307
self.disable_initiator_tag = None
567
308
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
568
310
self.current_checker_command = None
570
self.approvals_pending = 0
571
self.changedstate = multiprocessing_manager.Condition(
572
multiprocessing_manager.Lock())
573
self.client_structure = [attr
574
for attr in self.__dict__.iterkeys()
575
if not attr.startswith("_")]
576
self.client_structure.append("client_structure")
578
for name, t in inspect.getmembers(
579
type(self), lambda obj: isinstance(obj, property)):
580
if not name.startswith("_"):
581
self.client_structure.append(name)
583
# Send notice to process children that client state has changed
584
def send_changedstate(self):
585
with self.changedstate:
586
self.changedstate.notify_all()
311
self.last_connect = None
588
313
def enable(self):
589
314
"""Start this client's checker and timeout hooks"""
590
if getattr(self, "enabled", False):
315
if getattr(self, u"enabled", False):
591
316
# Already enabled
593
self.expires = datetime.datetime.utcnow() + self.timeout
595
318
self.last_enabled = datetime.datetime.utcnow()
597
self.send_changedstate()
319
# Schedule a new checker to be started an 'interval' from now,
320
# and every interval from then on.
321
self.checker_initiator_tag = (gobject.timeout_add
322
(self.interval_milliseconds(),
324
# Also start a new checker *right now*.
326
# Schedule a disable() when 'timeout' has passed
327
self.disable_initiator_tag = (gobject.timeout_add
328
(self.timeout_milliseconds(),
599
def disable(self, quiet=True):
600
333
"""Disable this client."""
601
334
if not getattr(self, "enabled", False):
604
logger.info("Disabling client %s", self.name)
605
if getattr(self, "disable_initiator_tag", None) is not None:
336
logger.info(u"Disabling client %s", self.name)
337
if getattr(self, u"disable_initiator_tag", False):
606
338
gobject.source_remove(self.disable_initiator_tag)
607
339
self.disable_initiator_tag = None
609
if getattr(self, "checker_initiator_tag", None) is not None:
340
if getattr(self, u"checker_initiator_tag", False):
610
341
gobject.source_remove(self.checker_initiator_tag)
611
342
self.checker_initiator_tag = None
612
343
self.stop_checker()
344
if self.disable_hook:
345
self.disable_hook(self)
613
346
self.enabled = False
615
self.send_changedstate()
616
347
# Do not run this again if called by a gobject.timeout_add
619
350
def __del__(self):
351
self.disable_hook = None
622
def init_checker(self):
623
# Schedule a new checker to be started an 'interval' from now,
624
# and every interval from then on.
625
if self.checker_initiator_tag is not None:
626
gobject.source_remove(self.checker_initiator_tag)
627
self.checker_initiator_tag = gobject.timeout_add(
628
int(self.interval.total_seconds() * 1000),
630
# Schedule a disable() when 'timeout' has passed
631
if self.disable_initiator_tag is not None:
632
gobject.source_remove(self.disable_initiator_tag)
633
self.disable_initiator_tag = gobject.timeout_add(
634
int(self.timeout.total_seconds() * 1000), self.disable)
635
# Also start a new checker *right now*.
638
354
def checker_callback(self, pid, condition, command):
639
355
"""The checker has completed, so take appropriate actions."""
640
356
self.checker_callback_tag = None
641
357
self.checker = None
642
358
if os.WIFEXITED(condition):
643
self.last_checker_status = os.WEXITSTATUS(condition)
644
if self.last_checker_status == 0:
645
logger.info("Checker for %(name)s succeeded",
359
exitstatus = os.WEXITSTATUS(condition)
361
logger.info(u"Checker for %(name)s succeeded",
647
363
self.checked_ok()
649
logger.info("Checker for %(name)s failed", vars(self))
365
logger.info(u"Checker for %(name)s failed",
651
self.last_checker_status = -1
652
logger.warning("Checker for %(name)s crashed?",
368
logger.warning(u"Checker for %(name)s crashed?",
655
371
def checked_ok(self):
656
"""Assert that the client has been seen, alive and well."""
372
"""Bump up the timeout for this client.
374
This should only be called when the client has been seen,
657
377
self.last_checked_ok = datetime.datetime.utcnow()
658
self.last_checker_status = 0
661
def bump_timeout(self, timeout=None):
662
"""Bump up the timeout for this client."""
664
timeout = self.timeout
665
if self.disable_initiator_tag is not None:
666
gobject.source_remove(self.disable_initiator_tag)
667
self.disable_initiator_tag = None
668
if getattr(self, "enabled", False):
669
self.disable_initiator_tag = gobject.timeout_add(
670
int(timeout.total_seconds() * 1000), self.disable)
671
self.expires = datetime.datetime.utcnow() + timeout
673
def need_approval(self):
674
self.last_approval_request = datetime.datetime.utcnow()
378
gobject.source_remove(self.disable_initiator_tag)
379
self.disable_initiator_tag = (gobject.timeout_add
380
(self.timeout_milliseconds(),
676
383
def start_checker(self):
677
384
"""Start a new checker subprocess if one is not running.
763
453
if self.checker_callback_tag:
764
454
gobject.source_remove(self.checker_callback_tag)
765
455
self.checker_callback_tag = None
766
if getattr(self, "checker", None) is None:
456
if getattr(self, u"checker", None) is None:
768
logger.debug("Stopping checker for %(name)s", vars(self))
458
logger.debug(u"Stopping checker for %(name)s", vars(self))
770
self.checker.terminate()
460
os.kill(self.checker.pid, signal.SIGTERM)
772
462
#if self.checker.poll() is None:
773
# self.checker.kill()
774
except OSError as error:
463
# os.kill(self.checker.pid, signal.SIGKILL)
464
except OSError, error:
775
465
if error.errno != errno.ESRCH: # No such process
777
467
self.checker = None
780
def dbus_service_property(dbus_interface,
784
"""Decorators for marking methods of a DBusObjectWithProperties to
785
become properties on the D-Bus.
787
The decorated method will be called with no arguments by "Get"
788
and with one argument by "Set".
790
The parameters, where they are supported, are the same as
791
dbus.service.method, except there is only "signature", since the
792
type from Get() and the type sent to Set() is the same.
794
# Encoding deeply encoded byte arrays is not supported yet by the
795
# "Set" method, so we fail early here:
796
if byte_arrays and signature != "ay":
797
raise ValueError("Byte arrays not supported for non-'ay'"
798
" signature {!r}".format(signature))
801
func._dbus_is_property = True
802
func._dbus_interface = dbus_interface
803
func._dbus_signature = signature
804
func._dbus_access = access
805
func._dbus_name = func.__name__
806
if func._dbus_name.endswith("_dbus_property"):
807
func._dbus_name = func._dbus_name[:-14]
808
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
814
def dbus_interface_annotations(dbus_interface):
815
"""Decorator for marking functions returning interface annotations
819
@dbus_interface_annotations("org.example.Interface")
820
def _foo(self): # Function name does not matter
821
return {"org.freedesktop.DBus.Deprecated": "true",
822
"org.freedesktop.DBus.Property.EmitsChangedSignal":
827
func._dbus_is_interface = True
828
func._dbus_interface = dbus_interface
829
func._dbus_name = dbus_interface
835
def dbus_annotations(annotations):
836
"""Decorator to annotate D-Bus methods, signals or properties
839
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
840
"org.freedesktop.DBus.Property."
841
"EmitsChangedSignal": "false"})
842
@dbus_service_property("org.example.Interface", signature="b",
844
def Property_dbus_property(self):
845
return dbus.Boolean(False)
849
func._dbus_annotations = annotations
855
class DBusPropertyException(dbus.exceptions.DBusException):
856
"""A base class for D-Bus property-related exceptions
861
class DBusPropertyAccessException(DBusPropertyException):
862
"""A property's access permissions disallows an operation.
867
class DBusPropertyNotFound(DBusPropertyException):
868
"""An attempt was made to access a non-existing property.
873
class DBusObjectWithProperties(dbus.service.Object):
874
"""A D-Bus object with properties.
876
Classes inheriting from this can use the dbus_service_property
877
decorator to expose methods as D-Bus properties. It exposes the
878
standard Get(), Set(), and GetAll() methods on the D-Bus.
882
def _is_dbus_thing(thing):
883
"""Returns a function testing if an attribute is a D-Bus thing
885
If called like _is_dbus_thing("method") it returns a function
886
suitable for use as predicate to inspect.getmembers().
888
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
891
def _get_all_dbus_things(self, thing):
892
"""Returns a generator of (name, attribute) pairs
894
return ((getattr(athing.__get__(self), "_dbus_name", name),
895
athing.__get__(self))
896
for cls in self.__class__.__mro__
898
inspect.getmembers(cls, self._is_dbus_thing(thing)))
900
def _get_dbus_property(self, interface_name, property_name):
901
"""Returns a bound method if one exists which is a D-Bus
902
property with the specified name and interface.
904
for cls in self.__class__.__mro__:
905
for name, value in inspect.getmembers(
906
cls, self._is_dbus_thing("property")):
907
if (value._dbus_name == property_name
908
and value._dbus_interface == interface_name):
909
return value.__get__(self)
912
raise DBusPropertyNotFound("{}:{}.{}".format(
913
self.dbus_object_path, interface_name, property_name))
915
@dbus.service.method(dbus.PROPERTIES_IFACE,
918
def Get(self, interface_name, property_name):
919
"""Standard D-Bus property Get() method, see D-Bus standard.
921
prop = self._get_dbus_property(interface_name, property_name)
922
if prop._dbus_access == "write":
923
raise DBusPropertyAccessException(property_name)
925
if not hasattr(value, "variant_level"):
927
return type(value)(value, variant_level=value.variant_level+1)
929
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
930
def Set(self, interface_name, property_name, value):
931
"""Standard D-Bus property Set() method, see D-Bus standard.
933
prop = self._get_dbus_property(interface_name, property_name)
934
if prop._dbus_access == "read":
935
raise DBusPropertyAccessException(property_name)
936
if prop._dbus_get_args_options["byte_arrays"]:
937
# The byte_arrays option is not supported yet on
938
# signatures other than "ay".
939
if prop._dbus_signature != "ay":
940
raise ValueError("Byte arrays not supported for non-"
941
"'ay' signature {!r}"
942
.format(prop._dbus_signature))
943
value = dbus.ByteArray(b''.join(chr(byte)
947
@dbus.service.method(dbus.PROPERTIES_IFACE,
949
out_signature="a{sv}")
950
def GetAll(self, interface_name):
951
"""Standard D-Bus property GetAll() method, see D-Bus
954
Note: Will not include properties with access="write".
957
for name, prop in self._get_all_dbus_things("property"):
959
and interface_name != prop._dbus_interface):
960
# Interface non-empty but did not match
962
# Ignore write-only properties
963
if prop._dbus_access == "write":
966
if not hasattr(value, "variant_level"):
967
properties[name] = value
969
properties[name] = type(value)(
970
value, variant_level = value.variant_level + 1)
971
return dbus.Dictionary(properties, signature="sv")
973
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
974
def PropertiesChanged(self, interface_name, changed_properties,
975
invalidated_properties):
976
"""Standard D-Bus PropertiesChanged() signal, see D-Bus
981
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
983
path_keyword='object_path',
984
connection_keyword='connection')
985
def Introspect(self, object_path, connection):
986
"""Overloading of standard D-Bus method.
988
Inserts property tags and interface annotation tags.
990
xmlstring = dbus.service.Object.Introspect(self, object_path,
993
document = xml.dom.minidom.parseString(xmlstring)
995
def make_tag(document, name, prop):
996
e = document.createElement("property")
997
e.setAttribute("name", name)
998
e.setAttribute("type", prop._dbus_signature)
999
e.setAttribute("access", prop._dbus_access)
1002
for if_tag in document.getElementsByTagName("interface"):
1004
for tag in (make_tag(document, name, prop)
1006
in self._get_all_dbus_things("property")
1007
if prop._dbus_interface
1008
== if_tag.getAttribute("name")):
1009
if_tag.appendChild(tag)
1010
# Add annotation tags
1011
for typ in ("method", "signal", "property"):
1012
for tag in if_tag.getElementsByTagName(typ):
1014
for name, prop in (self.
1015
_get_all_dbus_things(typ)):
1016
if (name == tag.getAttribute("name")
1017
and prop._dbus_interface
1018
== if_tag.getAttribute("name")):
1019
annots.update(getattr(
1020
prop, "_dbus_annotations", {}))
1021
for name, value in annots.items():
1022
ann_tag = document.createElement(
1024
ann_tag.setAttribute("name", name)
1025
ann_tag.setAttribute("value", value)
1026
tag.appendChild(ann_tag)
1027
# Add interface annotation tags
1028
for annotation, value in dict(
1029
itertools.chain.from_iterable(
1030
annotations().items()
1031
for name, annotations
1032
in self._get_all_dbus_things("interface")
1033
if name == if_tag.getAttribute("name")
1035
ann_tag = document.createElement("annotation")
1036
ann_tag.setAttribute("name", annotation)
1037
ann_tag.setAttribute("value", value)
1038
if_tag.appendChild(ann_tag)
1039
# Add the names to the return values for the
1040
# "org.freedesktop.DBus.Properties" methods
1041
if (if_tag.getAttribute("name")
1042
== "org.freedesktop.DBus.Properties"):
1043
for cn in if_tag.getElementsByTagName("method"):
1044
if cn.getAttribute("name") == "Get":
1045
for arg in cn.getElementsByTagName("arg"):
1046
if (arg.getAttribute("direction")
1048
arg.setAttribute("name", "value")
1049
elif cn.getAttribute("name") == "GetAll":
1050
for arg in cn.getElementsByTagName("arg"):
1051
if (arg.getAttribute("direction")
1053
arg.setAttribute("name", "props")
1054
xmlstring = document.toxml("utf-8")
1056
except (AttributeError, xml.dom.DOMException,
1057
xml.parsers.expat.ExpatError) as error:
1058
logger.error("Failed to override Introspection method",
1063
def datetime_to_dbus(dt, variant_level=0):
1064
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1066
return dbus.String("", variant_level = variant_level)
1067
return dbus.String(dt.isoformat(), variant_level=variant_level)
1070
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1071
"""A class decorator; applied to a subclass of
1072
dbus.service.Object, it will add alternate D-Bus attributes with
1073
interface names according to the "alt_interface_names" mapping.
1076
@alternate_dbus_interfaces({"org.example.Interface":
1077
"net.example.AlternateInterface"})
1078
class SampleDBusObject(dbus.service.Object):
1079
@dbus.service.method("org.example.Interface")
1080
def SampleDBusMethod():
1083
The above "SampleDBusMethod" on "SampleDBusObject" will be
1084
reachable via two interfaces: "org.example.Interface" and
1085
"net.example.AlternateInterface", the latter of which will have
1086
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1087
"true", unless "deprecate" is passed with a False value.
1089
This works for methods and signals, and also for D-Bus properties
1090
(from DBusObjectWithProperties) and interfaces (from the
1091
dbus_interface_annotations decorator).
1095
for orig_interface_name, alt_interface_name in (
1096
alt_interface_names.items()):
1098
interface_names = set()
1099
# Go though all attributes of the class
1100
for attrname, attribute in inspect.getmembers(cls):
1101
# Ignore non-D-Bus attributes, and D-Bus attributes
1102
# with the wrong interface name
1103
if (not hasattr(attribute, "_dbus_interface")
1104
or not attribute._dbus_interface.startswith(
1105
orig_interface_name)):
1107
# Create an alternate D-Bus interface name based on
1109
alt_interface = attribute._dbus_interface.replace(
1110
orig_interface_name, alt_interface_name)
1111
interface_names.add(alt_interface)
1112
# Is this a D-Bus signal?
1113
if getattr(attribute, "_dbus_is_signal", False):
1114
if sys.version == 2:
1115
# Extract the original non-method undecorated
1116
# function by black magic
1117
nonmethod_func = (dict(
1118
zip(attribute.func_code.co_freevars,
1119
attribute.__closure__))
1120
["func"].cell_contents)
1122
nonmethod_func = attribute
1123
# Create a new, but exactly alike, function
1124
# object, and decorate it to be a new D-Bus signal
1125
# with the alternate D-Bus interface name
1126
if sys.version == 2:
1127
new_function = types.FunctionType(
1128
nonmethod_func.func_code,
1129
nonmethod_func.func_globals,
1130
nonmethod_func.func_name,
1131
nonmethod_func.func_defaults,
1132
nonmethod_func.func_closure)
1134
new_function = types.FunctionType(
1135
nonmethod_func.__code__,
1136
nonmethod_func.__globals__,
1137
nonmethod_func.__name__,
1138
nonmethod_func.__defaults__,
1139
nonmethod_func.__closure__)
1140
new_function = (dbus.service.signal(
1142
attribute._dbus_signature)(new_function))
1143
# Copy annotations, if any
1145
new_function._dbus_annotations = dict(
1146
attribute._dbus_annotations)
1147
except AttributeError:
1149
# Define a creator of a function to call both the
1150
# original and alternate functions, so both the
1151
# original and alternate signals gets sent when
1152
# the function is called
1153
def fixscope(func1, func2):
1154
"""This function is a scope container to pass
1155
func1 and func2 to the "call_both" function
1156
outside of its arguments"""
1158
def call_both(*args, **kwargs):
1159
"""This function will emit two D-Bus
1160
signals by calling func1 and func2"""
1161
func1(*args, **kwargs)
1162
func2(*args, **kwargs)
1165
# Create the "call_both" function and add it to
1167
attr[attrname] = fixscope(attribute, new_function)
1168
# Is this a D-Bus method?
1169
elif getattr(attribute, "_dbus_is_method", False):
1170
# Create a new, but exactly alike, function
1171
# object. Decorate it to be a new D-Bus method
1172
# with the alternate D-Bus interface name. Add it
1175
dbus.service.method(
1177
attribute._dbus_in_signature,
1178
attribute._dbus_out_signature)
1179
(types.FunctionType(attribute.func_code,
1180
attribute.func_globals,
1181
attribute.func_name,
1182
attribute.func_defaults,
1183
attribute.func_closure)))
1184
# Copy annotations, if any
1186
attr[attrname]._dbus_annotations = dict(
1187
attribute._dbus_annotations)
1188
except AttributeError:
1190
# Is this a D-Bus property?
1191
elif getattr(attribute, "_dbus_is_property", False):
1192
# Create a new, but exactly alike, function
1193
# object, and decorate it to be a new D-Bus
1194
# property with the alternate D-Bus interface
1195
# name. Add it to the class.
1196
attr[attrname] = (dbus_service_property(
1197
alt_interface, attribute._dbus_signature,
1198
attribute._dbus_access,
1199
attribute._dbus_get_args_options
1201
(types.FunctionType(
1202
attribute.func_code,
1203
attribute.func_globals,
1204
attribute.func_name,
1205
attribute.func_defaults,
1206
attribute.func_closure)))
1207
# Copy annotations, if any
1209
attr[attrname]._dbus_annotations = dict(
1210
attribute._dbus_annotations)
1211
except AttributeError:
1213
# Is this a D-Bus interface?
1214
elif getattr(attribute, "_dbus_is_interface", False):
1215
# Create a new, but exactly alike, function
1216
# object. Decorate it to be a new D-Bus interface
1217
# with the alternate D-Bus interface name. Add it
1220
dbus_interface_annotations(alt_interface)
1221
(types.FunctionType(attribute.func_code,
1222
attribute.func_globals,
1223
attribute.func_name,
1224
attribute.func_defaults,
1225
attribute.func_closure)))
1227
# Deprecate all alternate interfaces
1228
iname="_AlternateDBusNames_interface_annotation{}"
1229
for interface_name in interface_names:
1231
@dbus_interface_annotations(interface_name)
1233
return { "org.freedesktop.DBus.Deprecated":
1235
# Find an unused name
1236
for aname in (iname.format(i)
1237
for i in itertools.count()):
1238
if aname not in attr:
1242
# Replace the class with a new subclass of it with
1243
# methods, signals, etc. as created above.
1244
cls = type(b"{}Alternate".format(cls.__name__),
1251
@alternate_dbus_interfaces({"se.recompile.Mandos":
1252
"se.bsnet.fukt.Mandos"})
1253
class ClientDBus(Client, DBusObjectWithProperties):
469
def still_valid(self):
470
"""Has the timeout not yet passed for this client?"""
471
if not getattr(self, u"enabled", False):
473
now = datetime.datetime.utcnow()
474
if self.last_checked_ok is None:
475
return now < (self.created + self.timeout)
477
return now < (self.last_checked_ok + self.timeout)
480
class ClientDBus(Client, dbus.service.Object):
1254
481
"""A Client class using D-Bus
1257
484
dbus_object_path: dbus.ObjectPath
1258
485
bus: dbus.SystemBus()
1261
runtime_expansions = (Client.runtime_expansions
1262
+ ("dbus_object_path", ))
1264
_interface = "se.recompile.Mandos.Client"
1266
487
# dbus.service.Object doesn't use super(), so we can't either.
1268
489
def __init__(self, bus = None, *args, **kwargs):
1270
491
Client.__init__(self, *args, **kwargs)
1271
492
# Only now, when this client is initialized, can it show up on
1273
client_object_name = str(self.name).translate(
1274
{ord("."): ord("_"),
1275
ord("-"): ord("_")})
1276
self.dbus_object_path = dbus.ObjectPath(
1277
"/clients/" + client_object_name)
1278
DBusObjectWithProperties.__init__(self, self.bus,
1279
self.dbus_object_path)
1281
def notifychangeproperty(transform_func, dbus_name,
1282
type_func=lambda x: x,
1284
invalidate_only=False,
1285
_interface=_interface):
1286
""" Modify a variable so that it's a property which announces
1287
its changes to DBus.
1289
transform_fun: Function that takes a value and a variant_level
1290
and transforms it to a D-Bus type.
1291
dbus_name: D-Bus name of the variable
1292
type_func: Function that transform the value before sending it
1293
to the D-Bus. Default: no transform
1294
variant_level: D-Bus variant level. Default: 1
1296
attrname = "_{}".format(dbus_name)
1298
def setter(self, value):
1299
if hasattr(self, "dbus_object_path"):
1300
if (not hasattr(self, attrname) or
1301
type_func(getattr(self, attrname, None))
1302
!= type_func(value)):
1304
self.PropertiesChanged(
1305
_interface, dbus.Dictionary(),
1306
dbus.Array((dbus_name, )))
1308
dbus_value = transform_func(
1310
variant_level = variant_level)
1311
self.PropertyChanged(dbus.String(dbus_name),
1313
self.PropertiesChanged(
1315
dbus.Dictionary({ dbus.String(dbus_name):
1318
setattr(self, attrname, value)
1320
return property(lambda self: getattr(self, attrname), setter)
1322
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1323
approvals_pending = notifychangeproperty(dbus.Boolean,
1326
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1327
last_enabled = notifychangeproperty(datetime_to_dbus,
1329
checker = notifychangeproperty(
1330
dbus.Boolean, "CheckerRunning",
1331
type_func = lambda checker: checker is not None)
1332
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1334
last_checker_status = notifychangeproperty(dbus.Int16,
1335
"LastCheckerStatus")
1336
last_approval_request = notifychangeproperty(
1337
datetime_to_dbus, "LastApprovalRequest")
1338
approved_by_default = notifychangeproperty(dbus.Boolean,
1339
"ApprovedByDefault")
1340
approval_delay = notifychangeproperty(
1341
dbus.UInt64, "ApprovalDelay",
1342
type_func = lambda td: td.total_seconds() * 1000)
1343
approval_duration = notifychangeproperty(
1344
dbus.UInt64, "ApprovalDuration",
1345
type_func = lambda td: td.total_seconds() * 1000)
1346
host = notifychangeproperty(dbus.String, "Host")
1347
timeout = notifychangeproperty(
1348
dbus.UInt64, "Timeout",
1349
type_func = lambda td: td.total_seconds() * 1000)
1350
extended_timeout = notifychangeproperty(
1351
dbus.UInt64, "ExtendedTimeout",
1352
type_func = lambda td: td.total_seconds() * 1000)
1353
interval = notifychangeproperty(
1354
dbus.UInt64, "Interval",
1355
type_func = lambda td: td.total_seconds() * 1000)
1356
checker_command = notifychangeproperty(dbus.String, "Checker")
1357
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1358
invalidate_only=True)
1360
del notifychangeproperty
494
self.dbus_object_path = (dbus.ObjectPath
496
+ self.name.replace(u".", u"_")))
497
dbus.service.Object.__init__(self, self.bus,
498
self.dbus_object_path)
501
def _datetime_to_dbus(dt, variant_level=0):
502
"""Convert a UTC datetime.datetime() to a D-Bus type."""
503
return dbus.String(dt.isoformat(),
504
variant_level=variant_level)
507
oldstate = getattr(self, u"enabled", False)
508
r = Client.enable(self)
509
if oldstate != self.enabled:
511
self.PropertyChanged(dbus.String(u"enabled"),
512
dbus.Boolean(True, variant_level=1))
513
self.PropertyChanged(
514
dbus.String(u"last_enabled"),
515
self._datetime_to_dbus(self.last_enabled,
519
def disable(self, signal = True):
520
oldstate = getattr(self, u"enabled", False)
521
r = Client.disable(self)
522
if signal and oldstate != self.enabled:
524
self.PropertyChanged(dbus.String(u"enabled"),
525
dbus.Boolean(False, variant_level=1))
1362
528
def __del__(self, *args, **kwargs):
1364
530
self.remove_from_connection()
1365
531
except LookupError:
1367
if hasattr(DBusObjectWithProperties, "__del__"):
1368
DBusObjectWithProperties.__del__(self, *args, **kwargs)
533
if hasattr(dbus.service.Object, u"__del__"):
534
dbus.service.Object.__del__(self, *args, **kwargs)
1369
535
Client.__del__(self, *args, **kwargs)
1371
537
def checker_callback(self, pid, condition, command,
1372
538
*args, **kwargs):
1373
539
self.checker_callback_tag = None
1374
540
self.checker = None
542
self.PropertyChanged(dbus.String(u"checker_running"),
543
dbus.Boolean(False, variant_level=1))
1375
544
if os.WIFEXITED(condition):
1376
545
exitstatus = os.WEXITSTATUS(condition)
1377
546
# Emit D-Bus signal
1387
556
return Client.checker_callback(self, pid, condition, command,
1388
557
*args, **kwargs)
559
def checked_ok(self, *args, **kwargs):
560
r = Client.checked_ok(self, *args, **kwargs)
562
self.PropertyChanged(
563
dbus.String(u"last_checked_ok"),
564
(self._datetime_to_dbus(self.last_checked_ok,
1390
568
def start_checker(self, *args, **kwargs):
1391
old_checker_pid = getattr(self.checker, "pid", None)
569
old_checker = self.checker
570
if self.checker is not None:
571
old_checker_pid = self.checker.pid
573
old_checker_pid = None
1392
574
r = Client.start_checker(self, *args, **kwargs)
1393
575
# Only if new checker process was started
1394
576
if (self.checker is not None
1395
577
and old_checker_pid != self.checker.pid):
1396
578
# Emit D-Bus signal
1397
579
self.CheckerStarted(self.current_checker_command)
1400
def _reset_approved(self):
1401
self.approved = None
1404
def approve(self, value=True):
1405
self.approved = value
1406
gobject.timeout_add(int(self.approval_duration.total_seconds()
1407
* 1000), self._reset_approved)
1408
self.send_changedstate()
1410
## D-Bus methods, signals & properties
580
self.PropertyChanged(
581
dbus.String(u"checker_running"),
582
dbus.Boolean(True, variant_level=1))
585
def stop_checker(self, *args, **kwargs):
586
old_checker = getattr(self, u"checker", None)
587
r = Client.stop_checker(self, *args, **kwargs)
588
if (old_checker is not None
589
and getattr(self, u"checker", None) is None):
590
self.PropertyChanged(dbus.String(u"checker_running"),
591
dbus.Boolean(False, variant_level=1))
594
## D-Bus methods & signals
595
_interface = u"se.bsnet.fukt.Mandos.Client"
598
@dbus.service.method(_interface)
600
return self.checked_ok()
1416
602
# CheckerCompleted - signal
1417
@dbus.service.signal(_interface, signature="nxs")
603
@dbus.service.signal(_interface, signature=u"nxs")
1418
604
def CheckerCompleted(self, exitcode, waitstatus, command):
1422
608
# CheckerStarted - signal
1423
@dbus.service.signal(_interface, signature="s")
609
@dbus.service.signal(_interface, signature=u"s")
1424
610
def CheckerStarted(self, command):
614
# GetAllProperties - method
615
@dbus.service.method(_interface, out_signature=u"a{sv}")
616
def GetAllProperties(self):
618
return dbus.Dictionary({
619
dbus.String(u"name"):
620
dbus.String(self.name, variant_level=1),
621
dbus.String(u"fingerprint"):
622
dbus.String(self.fingerprint, variant_level=1),
623
dbus.String(u"host"):
624
dbus.String(self.host, variant_level=1),
625
dbus.String(u"created"):
626
self._datetime_to_dbus(self.created,
628
dbus.String(u"last_enabled"):
629
(self._datetime_to_dbus(self.last_enabled,
631
if self.last_enabled is not None
632
else dbus.Boolean(False, variant_level=1)),
633
dbus.String(u"enabled"):
634
dbus.Boolean(self.enabled, variant_level=1),
635
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
638
if self.last_checked_ok is not None
639
else dbus.Boolean (False, variant_level=1)),
640
dbus.String(u"timeout"):
641
dbus.UInt64(self.timeout_milliseconds(),
643
dbus.String(u"interval"):
644
dbus.UInt64(self.interval_milliseconds(),
646
dbus.String(u"checker"):
647
dbus.String(self.checker_command,
649
dbus.String(u"checker_running"):
650
dbus.Boolean(self.checker is not None,
652
dbus.String(u"object_path"):
653
dbus.ObjectPath(self.dbus_object_path,
657
# IsStillValid - method
658
@dbus.service.method(_interface, out_signature=u"b")
659
def IsStillValid(self):
660
return self.still_valid()
1428
662
# PropertyChanged - signal
1429
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1430
@dbus.service.signal(_interface, signature="sv")
663
@dbus.service.signal(_interface, signature=u"sv")
1431
664
def PropertyChanged(self, property, value):
1435
# GotSecret - signal
668
# ReceivedSecret - signal
1436
669
@dbus.service.signal(_interface)
1437
def GotSecret(self):
1439
Is sent after a successful transfer of secret from the Mandos
1440
server to mandos-client
670
def ReceivedSecret(self):
1444
674
# Rejected - signal
1445
@dbus.service.signal(_interface, signature="s")
1446
def Rejected(self, reason):
675
@dbus.service.signal(_interface)
1450
# NeedApproval - signal
1451
@dbus.service.signal(_interface, signature="tb")
1452
def NeedApproval(self, timeout, default):
1454
return self.need_approval()
1459
@dbus.service.method(_interface, in_signature="b")
1460
def Approve(self, value):
1463
# CheckedOK - method
1464
@dbus.service.method(_interface)
1465
def CheckedOK(self):
680
# SetChecker - method
681
@dbus.service.method(_interface, in_signature=u"s")
682
def SetChecker(self, checker):
683
"D-Bus setter method"
684
self.checker_command = checker
686
self.PropertyChanged(dbus.String(u"checker"),
687
dbus.String(self.checker_command,
691
@dbus.service.method(_interface, in_signature=u"s")
692
def SetHost(self, host):
693
"D-Bus setter method"
696
self.PropertyChanged(dbus.String(u"host"),
697
dbus.String(self.host, variant_level=1))
699
# SetInterval - method
700
@dbus.service.method(_interface, in_signature=u"t")
701
def SetInterval(self, milliseconds):
702
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
704
self.PropertyChanged(dbus.String(u"interval"),
705
(dbus.UInt64(self.interval_milliseconds(),
709
@dbus.service.method(_interface, in_signature=u"ay",
711
def SetSecret(self, secret):
712
"D-Bus setter method"
713
self.secret = str(secret)
715
# SetTimeout - method
716
@dbus.service.method(_interface, in_signature=u"t")
717
def SetTimeout(self, milliseconds):
718
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
720
self.PropertyChanged(dbus.String(u"timeout"),
721
(dbus.UInt64(self.timeout_milliseconds(),
1468
724
# Enable - method
1469
725
@dbus.service.method(_interface)
1488
744
def StopChecker(self):
1489
745
self.stop_checker()
1493
# ApprovalPending - property
1494
@dbus_service_property(_interface, signature="b", access="read")
1495
def ApprovalPending_dbus_property(self):
1496
return dbus.Boolean(bool(self.approvals_pending))
1498
# ApprovedByDefault - property
1499
@dbus_service_property(_interface,
1502
def ApprovedByDefault_dbus_property(self, value=None):
1503
if value is None: # get
1504
return dbus.Boolean(self.approved_by_default)
1505
self.approved_by_default = bool(value)
1507
# ApprovalDelay - property
1508
@dbus_service_property(_interface,
1511
def ApprovalDelay_dbus_property(self, value=None):
1512
if value is None: # get
1513
return dbus.UInt64(self.approval_delay.total_seconds()
1515
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1517
# ApprovalDuration - property
1518
@dbus_service_property(_interface,
1521
def ApprovalDuration_dbus_property(self, value=None):
1522
if value is None: # get
1523
return dbus.UInt64(self.approval_duration.total_seconds()
1525
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1528
@dbus_service_property(_interface, signature="s", access="read")
1529
def Name_dbus_property(self):
1530
return dbus.String(self.name)
1532
# Fingerprint - property
1533
@dbus_service_property(_interface, signature="s", access="read")
1534
def Fingerprint_dbus_property(self):
1535
return dbus.String(self.fingerprint)
1538
@dbus_service_property(_interface,
1541
def Host_dbus_property(self, value=None):
1542
if value is None: # get
1543
return dbus.String(self.host)
1544
self.host = str(value)
1546
# Created - property
1547
@dbus_service_property(_interface, signature="s", access="read")
1548
def Created_dbus_property(self):
1549
return datetime_to_dbus(self.created)
1551
# LastEnabled - property
1552
@dbus_service_property(_interface, signature="s", access="read")
1553
def LastEnabled_dbus_property(self):
1554
return datetime_to_dbus(self.last_enabled)
1556
# Enabled - property
1557
@dbus_service_property(_interface,
1560
def Enabled_dbus_property(self, value=None):
1561
if value is None: # get
1562
return dbus.Boolean(self.enabled)
1568
# LastCheckedOK - property
1569
@dbus_service_property(_interface,
1572
def LastCheckedOK_dbus_property(self, value=None):
1573
if value is not None:
1576
return datetime_to_dbus(self.last_checked_ok)
1578
# LastCheckerStatus - property
1579
@dbus_service_property(_interface, signature="n", access="read")
1580
def LastCheckerStatus_dbus_property(self):
1581
return dbus.Int16(self.last_checker_status)
1583
# Expires - property
1584
@dbus_service_property(_interface, signature="s", access="read")
1585
def Expires_dbus_property(self):
1586
return datetime_to_dbus(self.expires)
1588
# LastApprovalRequest - property
1589
@dbus_service_property(_interface, signature="s", access="read")
1590
def LastApprovalRequest_dbus_property(self):
1591
return datetime_to_dbus(self.last_approval_request)
1593
# Timeout - property
1594
@dbus_service_property(_interface,
1597
def Timeout_dbus_property(self, value=None):
1598
if value is None: # get
1599
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1600
old_timeout = self.timeout
1601
self.timeout = datetime.timedelta(0, 0, 0, value)
1602
# Reschedule disabling
1604
now = datetime.datetime.utcnow()
1605
self.expires += self.timeout - old_timeout
1606
if self.expires <= now:
1607
# The timeout has passed
1610
if (getattr(self, "disable_initiator_tag", None)
1613
gobject.source_remove(self.disable_initiator_tag)
1614
self.disable_initiator_tag = gobject.timeout_add(
1615
int((self.expires - now).total_seconds() * 1000),
1618
# ExtendedTimeout - property
1619
@dbus_service_property(_interface,
1622
def ExtendedTimeout_dbus_property(self, value=None):
1623
if value is None: # get
1624
return dbus.UInt64(self.extended_timeout.total_seconds()
1626
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1628
# Interval - property
1629
@dbus_service_property(_interface,
1632
def Interval_dbus_property(self, value=None):
1633
if value is None: # get
1634
return dbus.UInt64(self.interval.total_seconds() * 1000)
1635
self.interval = datetime.timedelta(0, 0, 0, value)
1636
if getattr(self, "checker_initiator_tag", None) is None:
1639
# Reschedule checker run
1640
gobject.source_remove(self.checker_initiator_tag)
1641
self.checker_initiator_tag = gobject.timeout_add(
1642
value, self.start_checker)
1643
self.start_checker() # Start one now, too
1645
# Checker - property
1646
@dbus_service_property(_interface,
1649
def Checker_dbus_property(self, value=None):
1650
if value is None: # get
1651
return dbus.String(self.checker_command)
1652
self.checker_command = str(value)
1654
# CheckerRunning - property
1655
@dbus_service_property(_interface,
1658
def CheckerRunning_dbus_property(self, value=None):
1659
if value is None: # get
1660
return dbus.Boolean(self.checker is not None)
1662
self.start_checker()
1666
# ObjectPath - property
1667
@dbus_service_property(_interface, signature="o", access="read")
1668
def ObjectPath_dbus_property(self):
1669
return self.dbus_object_path # is already a dbus.ObjectPath
1672
@dbus_service_property(_interface,
1676
def Secret_dbus_property(self, value):
1677
self.secret = bytes(value)
1682
class ProxyClient(object):
1683
def __init__(self, child_pipe, fpr, address):
1684
self._pipe = child_pipe
1685
self._pipe.send(('init', fpr, address))
1686
if not self._pipe.recv():
1689
def __getattribute__(self, name):
1691
return super(ProxyClient, self).__getattribute__(name)
1692
self._pipe.send(('getattr', name))
1693
data = self._pipe.recv()
1694
if data[0] == 'data':
1696
if data[0] == 'function':
1698
def func(*args, **kwargs):
1699
self._pipe.send(('funcall', name, args, kwargs))
1700
return self._pipe.recv()[1]
1704
def __setattr__(self, name, value):
1706
return super(ProxyClient, self).__setattr__(name, value)
1707
self._pipe.send(('setattr', name, value))
1710
750
class ClientHandler(socketserver.BaseRequestHandler, object):
1711
751
"""A class to handle client connections.
1714
754
Note: This will run in its own forked process."""
1716
756
def handle(self):
1717
with contextlib.closing(self.server.child_pipe) as child_pipe:
1718
logger.info("TCP connection from: %s",
1719
str(self.client_address))
1720
logger.debug("Pipe FD: %d",
1721
self.server.child_pipe.fileno())
757
logger.info(u"TCP connection from: %s",
758
unicode(self.client_address))
759
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
760
# Open IPC pipe to parent process
761
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
762
session = (gnutls.connection
763
.ClientSession(self.request,
1723
session = gnutls.connection.ClientSession(
1724
self.request, gnutls.connection .X509Credentials())
767
line = self.request.makefile().readline()
768
logger.debug(u"Protocol version: %r", line)
770
if int(line.strip().split()[0]) > 1:
772
except (ValueError, IndexError, RuntimeError), error:
773
logger.error(u"Unknown protocol version: %s", error)
1726
776
# Note: gnutls.connection.X509Credentials is really a
1727
777
# generic GnuTLS certificate credentials object so long as
1728
778
# no X.509 keys are added to it. Therefore, we can use it
1729
779
# here despite using OpenPGP certificates.
1731
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1732
# "+AES-256-CBC", "+SHA1",
1733
# "+COMP-NULL", "+CTYPE-OPENPGP",
781
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
782
# u"+AES-256-CBC", u"+SHA1",
783
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1735
785
# Use a fallback default, since this MUST be set.
1736
786
priority = self.server.gnutls_priority
1737
787
if priority is None:
1739
gnutls.library.functions.gnutls_priority_set_direct(
1740
session._c_object, priority, None)
1742
# Start communication using the Mandos protocol
1743
# Get protocol number
1744
line = self.request.makefile().readline()
1745
logger.debug("Protocol version: %r", line)
1747
if int(line.strip().split()[0]) > 1:
1748
raise RuntimeError(line)
1749
except (ValueError, IndexError, RuntimeError) as error:
1750
logger.error("Unknown protocol version: %s", error)
1753
# Start GnuTLS connection
789
(gnutls.library.functions
790
.gnutls_priority_set_direct(session._c_object,
1755
794
session.handshake()
1756
except gnutls.errors.GNUTLSError as error:
1757
logger.warning("Handshake failed: %s", error)
795
except gnutls.errors.GNUTLSError, error:
796
logger.warning(u"Handshake failed: %s", error)
1758
797
# Do not run session.bye() here: the session is not
1759
798
# established. Just abandon the request.
1761
logger.debug("Handshake succeeded")
1763
approval_required = False
800
logger.debug(u"Handshake succeeded")
1766
fpr = self.fingerprint(
1767
self.peer_certificate(session))
1769
gnutls.errors.GNUTLSError) as error:
1770
logger.warning("Bad certificate: %s", error)
1772
logger.debug("Fingerprint: %s", fpr)
1775
client = ProxyClient(child_pipe, fpr,
1776
self.client_address)
1780
if client.approval_delay:
1781
delay = client.approval_delay
1782
client.approvals_pending += 1
1783
approval_required = True
1786
if not client.enabled:
1787
logger.info("Client %s is disabled",
1789
if self.server.use_dbus:
1791
client.Rejected("Disabled")
1794
if client.approved or not client.approval_delay:
1795
#We are approved or approval is disabled
1797
elif client.approved is None:
1798
logger.info("Client %s needs approval",
1800
if self.server.use_dbus:
1802
client.NeedApproval(
1803
client.approval_delay.total_seconds()
1804
* 1000, client.approved_by_default)
1806
logger.warning("Client %s was not approved",
1808
if self.server.use_dbus:
1810
client.Rejected("Denied")
1813
#wait until timeout or approved
1814
time = datetime.datetime.now()
1815
client.changedstate.acquire()
1816
client.changedstate.wait(delay.total_seconds())
1817
client.changedstate.release()
1818
time2 = datetime.datetime.now()
1819
if (time2 - time) >= delay:
1820
if not client.approved_by_default:
1821
logger.warning("Client %s timed out while"
1822
" waiting for approval",
1824
if self.server.use_dbus:
1826
client.Rejected("Approval timed out")
1831
delay -= time2 - time
1834
while sent_size < len(client.secret):
1836
sent = session.send(client.secret[sent_size:])
1837
except gnutls.errors.GNUTLSError as error:
1838
logger.warning("gnutls send failed",
1841
logger.debug("Sent: %d, remaining: %d", sent,
1842
len(client.secret) - (sent_size
1846
logger.info("Sending secret to %s", client.name)
1847
# bump the timeout using extended_timeout
1848
client.bump_timeout(client.extended_timeout)
1849
if self.server.use_dbus:
802
fpr = self.fingerprint(self.peer_certificate(session))
803
except (TypeError, gnutls.errors.GNUTLSError), error:
804
logger.warning(u"Bad certificate: %s", error)
807
logger.debug(u"Fingerprint: %s", fpr)
1854
if approval_required:
1855
client.approvals_pending -= 1
1858
except gnutls.errors.GNUTLSError as error:
1859
logger.warning("GnuTLS bye failed",
809
for c in self.server.clients:
810
if c.fingerprint == fpr:
814
ipc.write(u"NOTFOUND %s %s\n"
815
% (fpr, unicode(self.client_address)))
818
# Have to check if client.still_valid(), since it is
819
# possible that the client timed out while establishing
820
# the GnuTLS session.
821
if not client.still_valid():
822
ipc.write(u"INVALID %s\n" % client.name)
825
ipc.write(u"SENDING %s\n" % client.name)
827
while sent_size < len(client.secret):
828
sent = session.send(client.secret[sent_size:])
829
logger.debug(u"Sent: %d, remaining: %d",
830
sent, len(client.secret)
831
- (sent_size + sent))
1863
836
def peer_certificate(session):
1864
837
"Return the peer's OpenPGP certificate as a bytestring"
1865
838
# If not an OpenPGP certificate...
1866
if (gnutls.library.functions.gnutls_certificate_type_get(
839
if (gnutls.library.functions
840
.gnutls_certificate_type_get(session._c_object)
1868
841
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1869
842
# ...do the normal thing
1870
843
return session.peer_certificate
2060
983
clients: set of Client objects
2061
984
gnutls_priority GnuTLS priority string
2062
985
use_dbus: Boolean; to emit D-Bus signals or not
986
clients: set of Client objects
987
gnutls_priority GnuTLS priority string
988
use_dbus: Boolean; to emit D-Bus signals or not
2064
990
Assumes a gobject.MainLoop event loop.
2067
992
def __init__(self, server_address, RequestHandlerClass,
2071
gnutls_priority=None,
993
interface=None, use_ipv6=True, clients=None,
994
gnutls_priority=None, use_dbus=True):
2074
995
self.enabled = False
2075
996
self.clients = clients
2076
997
if self.clients is None:
2078
999
self.use_dbus = use_dbus
2079
1000
self.gnutls_priority = gnutls_priority
2080
1001
IPv6_TCPServer.__init__(self, server_address,
2081
1002
RequestHandlerClass,
2082
1003
interface = interface,
2083
use_ipv6 = use_ipv6,
2084
socketfd = socketfd)
1004
use_ipv6 = use_ipv6)
2086
1005
def server_activate(self):
2087
1006
if self.enabled:
2088
1007
return socketserver.TCPServer.server_activate(self)
2090
1008
def enable(self):
2091
1009
self.enabled = True
2093
def add_pipe(self, parent_pipe, proc):
1010
def add_pipe(self, pipe):
2094
1011
# Call "handle_ipc" for both data and EOF events
2095
gobject.io_add_watch(
2096
parent_pipe.fileno(),
2097
gobject.IO_IN | gobject.IO_HUP,
2098
functools.partial(self.handle_ipc,
2099
parent_pipe = parent_pipe,
2102
def handle_ipc(self, source, condition,
2105
client_object=None):
2106
# error, or the other end of multiprocessing.Pipe has closed
2107
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2108
# Wait for other process to exit
2112
# Read a request from the child
2113
request = parent_pipe.recv()
2114
command = request[0]
2116
if command == 'init':
2118
address = request[2]
2120
for c in self.clients.itervalues():
2121
if c.fingerprint == fpr:
2125
logger.info("Client not found for fingerprint: %s, ad"
2126
"dress: %s", fpr, address)
2129
mandos_dbus_service.ClientNotFound(fpr,
2131
parent_pipe.send(False)
2134
gobject.io_add_watch(
2135
parent_pipe.fileno(),
2136
gobject.IO_IN | gobject.IO_HUP,
2137
functools.partial(self.handle_ipc,
2138
parent_pipe = parent_pipe,
2140
client_object = client))
2141
parent_pipe.send(True)
2142
# remove the old hook in favor of the new above hook on
2145
if command == 'funcall':
2146
funcname = request[1]
2150
parent_pipe.send(('data', getattr(client_object,
2154
if command == 'getattr':
2155
attrname = request[1]
2156
if isinstance(client_object.__getattribute__(attrname),
2157
collections.Callable):
2158
parent_pipe.send(('function', ))
2161
'data', client_object.__getattribute__(attrname)))
2163
if command == 'setattr':
2164
attrname = request[1]
2166
setattr(client_object, attrname, value)
1012
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1014
def handle_ipc(self, source, condition, file_objects={}):
1016
gobject.IO_IN: u"IN", # There is data to read.
1017
gobject.IO_OUT: u"OUT", # Data can be written (without
1019
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1020
gobject.IO_ERR: u"ERR", # Error condition.
1021
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1022
# broken, usually for pipes and
1025
conditions_string = ' | '.join(name
1027
condition_names.iteritems()
1028
if cond & condition)
1029
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1032
# Turn the pipe file descriptor into a Python file object
1033
if source not in file_objects:
1034
file_objects[source] = os.fdopen(source, u"r", 1)
1036
# Read a line from the file object
1037
cmdline = file_objects[source].readline()
1038
if not cmdline: # Empty line means end of file
1039
# close the IPC pipe
1040
file_objects[source].close()
1041
del file_objects[source]
1043
# Stop calling this function
1046
logger.debug(u"IPC command: %r", cmdline)
1048
# Parse and act on command
1049
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1051
if cmd == u"NOTFOUND":
1052
logger.warning(u"Client not found for fingerprint: %s",
1056
mandos_dbus_service.ClientNotFound(args)
1057
elif cmd == u"INVALID":
1058
for client in self.clients:
1059
if client.name == args:
1060
logger.warning(u"Client %s is invalid", args)
1066
logger.error(u"Unknown client %s is invalid", args)
1067
elif cmd == u"SENDING":
1068
for client in self.clients:
1069
if client.name == args:
1070
logger.info(u"Sending secret to %s", client.name)
1074
client.ReceivedSecret()
1077
logger.error(u"Sending secret to unknown client %s",
1080
logger.error(u"Unknown IPC command: %r", cmdline)
1082
# Keep calling this function
2171
def rfc3339_duration_to_delta(duration):
2172
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2174
>>> rfc3339_duration_to_delta("P7D")
2175
datetime.timedelta(7)
2176
>>> rfc3339_duration_to_delta("PT60S")
2177
datetime.timedelta(0, 60)
2178
>>> rfc3339_duration_to_delta("PT60M")
2179
datetime.timedelta(0, 3600)
2180
>>> rfc3339_duration_to_delta("PT24H")
2181
datetime.timedelta(1)
2182
>>> rfc3339_duration_to_delta("P1W")
2183
datetime.timedelta(7)
2184
>>> rfc3339_duration_to_delta("PT5M30S")
2185
datetime.timedelta(0, 330)
2186
>>> rfc3339_duration_to_delta("P1DT3M20S")
2187
datetime.timedelta(1, 200)
2190
# Parsing an RFC 3339 duration with regular expressions is not
2191
# possible - there would have to be multiple places for the same
2192
# values, like seconds. The current code, while more esoteric, is
2193
# cleaner without depending on a parsing library. If Python had a
2194
# built-in library for parsing we would use it, but we'd like to
2195
# avoid excessive use of external libraries.
2197
# New type for defining tokens, syntax, and semantics all-in-one
2198
Token = collections.namedtuple("Token", (
2199
"regexp", # To match token; if "value" is not None, must have
2200
# a "group" containing digits
2201
"value", # datetime.timedelta or None
2202
"followers")) # Tokens valid after this token
2203
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2204
# the "duration" ABNF definition in RFC 3339, Appendix A.
2205
token_end = Token(re.compile(r"$"), None, frozenset())
2206
token_second = Token(re.compile(r"(\d+)S"),
2207
datetime.timedelta(seconds=1),
2208
frozenset((token_end, )))
2209
token_minute = Token(re.compile(r"(\d+)M"),
2210
datetime.timedelta(minutes=1),
2211
frozenset((token_second, token_end)))
2212
token_hour = Token(re.compile(r"(\d+)H"),
2213
datetime.timedelta(hours=1),
2214
frozenset((token_minute, token_end)))
2215
token_time = Token(re.compile(r"T"),
2217
frozenset((token_hour, token_minute,
2219
token_day = Token(re.compile(r"(\d+)D"),
2220
datetime.timedelta(days=1),
2221
frozenset((token_time, token_end)))
2222
token_month = Token(re.compile(r"(\d+)M"),
2223
datetime.timedelta(weeks=4),
2224
frozenset((token_day, token_end)))
2225
token_year = Token(re.compile(r"(\d+)Y"),
2226
datetime.timedelta(weeks=52),
2227
frozenset((token_month, token_end)))
2228
token_week = Token(re.compile(r"(\d+)W"),
2229
datetime.timedelta(weeks=1),
2230
frozenset((token_end, )))
2231
token_duration = Token(re.compile(r"P"), None,
2232
frozenset((token_year, token_month,
2233
token_day, token_time,
2235
# Define starting values
2236
value = datetime.timedelta() # Value so far
2238
followers = frozenset((token_duration, )) # Following valid tokens
2239
s = duration # String left to parse
2240
# Loop until end token is found
2241
while found_token is not token_end:
2242
# Search for any currently valid tokens
2243
for token in followers:
2244
match = token.regexp.match(s)
2245
if match is not None:
2247
if token.value is not None:
2248
# Value found, parse digits
2249
factor = int(match.group(1), 10)
2250
# Add to value so far
2251
value += factor * token.value
2252
# Strip token from string
2253
s = token.regexp.sub("", s, 1)
2256
# Set valid next tokens
2257
followers = found_token.followers
2260
# No currently valid tokens were found
2261
raise ValueError("Invalid RFC 3339 duration: {!r}"
2267
1086
def string_to_delta(interval):
2268
1087
"""Parse a string and return a datetime.timedelta
2270
>>> string_to_delta('7d')
1089
>>> string_to_delta(u'7d')
2271
1090
datetime.timedelta(7)
2272
>>> string_to_delta('60s')
1091
>>> string_to_delta(u'60s')
2273
1092
datetime.timedelta(0, 60)
2274
>>> string_to_delta('60m')
1093
>>> string_to_delta(u'60m')
2275
1094
datetime.timedelta(0, 3600)
2276
>>> string_to_delta('24h')
1095
>>> string_to_delta(u'24h')
2277
1096
datetime.timedelta(1)
2278
>>> string_to_delta('1w')
1097
>>> string_to_delta(u'1w')
2279
1098
datetime.timedelta(7)
2280
>>> string_to_delta('5m 30s')
1099
>>> string_to_delta(u'5m 30s')
2281
1100
datetime.timedelta(0, 330)
2285
return rfc3339_duration_to_delta(interval)
2289
1102
timevalue = datetime.timedelta(0)
2290
1103
for s in interval.split():
1105
suffix = unicode(s[-1])
2293
1106
value = int(s[:-1])
2295
1108
delta = datetime.timedelta(value)
1109
elif suffix == u"s":
2297
1110
delta = datetime.timedelta(0, value)
1111
elif suffix == u"m":
2299
1112
delta = datetime.timedelta(0, 0, 0, 0, value)
1113
elif suffix == u"h":
2301
1114
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1115
elif suffix == u"w":
2303
1116
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2305
raise ValueError("Unknown suffix {!r}".format(suffix))
2306
except IndexError as e:
2307
raise ValueError(*(e.args))
1119
except (ValueError, IndexError):
2308
1121
timevalue += delta
2309
1122
return timevalue
1125
def if_nametoindex(interface):
1126
"""Call the C function if_nametoindex(), or equivalent
1128
Note: This function cannot accept a unicode string."""
1129
global if_nametoindex
1131
if_nametoindex = (ctypes.cdll.LoadLibrary
1132
(ctypes.util.find_library(u"c"))
1134
except (OSError, AttributeError):
1135
logger.warning(u"Doing if_nametoindex the hard way")
1136
def if_nametoindex(interface):
1137
"Get an interface index the hard way, i.e. using fcntl()"
1138
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1139
with closing(socket.socket()) as s:
1140
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1141
struct.pack(str(u"16s16x"),
1143
interface_index = struct.unpack(str(u"I"),
1145
return interface_index
1146
return if_nametoindex(interface)
2312
1149
def daemon(nochdir = False, noclose = False):
2313
1150
"""See daemon(3). Standard BSD Unix function.
2339
##################################################################
1175
######################################################################
2340
1176
# Parsing of options, both command line and config file
2342
parser = argparse.ArgumentParser()
2343
parser.add_argument("-v", "--version", action="version",
2344
version = "%(prog)s {}".format(version),
2345
help="show version number and exit")
2346
parser.add_argument("-i", "--interface", metavar="IF",
2347
help="Bind to interface IF")
2348
parser.add_argument("-a", "--address",
2349
help="Address to listen for requests on")
2350
parser.add_argument("-p", "--port", type=int,
2351
help="Port number to receive requests on")
2352
parser.add_argument("--check", action="store_true",
2353
help="Run self-test")
2354
parser.add_argument("--debug", action="store_true",
2355
help="Debug mode; run in foreground and log"
2356
" to terminal", default=None)
2357
parser.add_argument("--debuglevel", metavar="LEVEL",
2358
help="Debug level for stdout output")
2359
parser.add_argument("--priority", help="GnuTLS"
2360
" priority string (see GnuTLS documentation)")
2361
parser.add_argument("--servicename",
2362
metavar="NAME", help="Zeroconf service name")
2363
parser.add_argument("--configdir",
2364
default="/etc/mandos", metavar="DIR",
2365
help="Directory to search for configuration"
2367
parser.add_argument("--no-dbus", action="store_false",
2368
dest="use_dbus", help="Do not provide D-Bus"
2369
" system bus interface", default=None)
2370
parser.add_argument("--no-ipv6", action="store_false",
2371
dest="use_ipv6", help="Do not use IPv6",
2373
parser.add_argument("--no-restore", action="store_false",
2374
dest="restore", help="Do not restore stored"
2375
" state", default=None)
2376
parser.add_argument("--socket", type=int,
2377
help="Specify a file descriptor to a network"
2378
" socket to use instead of creating one")
2379
parser.add_argument("--statedir", metavar="DIR",
2380
help="Directory to save/restore state in")
2381
parser.add_argument("--foreground", action="store_true",
2382
help="Run in foreground", default=None)
2383
parser.add_argument("--no-zeroconf", action="store_false",
2384
dest="zeroconf", help="Do not use Zeroconf",
2387
options = parser.parse_args()
1178
parser = optparse.OptionParser(version = "%%prog %s" % version)
1179
parser.add_option("-i", u"--interface", type=u"string",
1180
metavar="IF", help=u"Bind to interface IF")
1181
parser.add_option("-a", u"--address", type=u"string",
1182
help=u"Address to listen for requests on")
1183
parser.add_option("-p", u"--port", type=u"int",
1184
help=u"Port number to receive requests on")
1185
parser.add_option("--check", action=u"store_true",
1186
help=u"Run self-test")
1187
parser.add_option("--debug", action=u"store_true",
1188
help=u"Debug mode; run in foreground and log to"
1190
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1191
u" priority string (see GnuTLS documentation)")
1192
parser.add_option("--servicename", type=u"string",
1193
metavar=u"NAME", help=u"Zeroconf service name")
1194
parser.add_option("--configdir", type=u"string",
1195
default=u"/etc/mandos", metavar=u"DIR",
1196
help=u"Directory to search for configuration"
1198
parser.add_option("--no-dbus", action=u"store_false",
1200
help=optparse.SUPPRESS_HELP) # XXX: Not done yet
1201
parser.add_option("--no-ipv6", action=u"store_false",
1202
dest=u"use_ipv6", help=u"Do not use IPv6")
1203
options = parser.parse_args()[0]
2389
1205
if options.check:
2391
fail_count, test_count = doctest.testmod()
2392
sys.exit(os.EX_OK if fail_count == 0 else 1)
2394
1210
# Default values for config file for server-global settings
2395
server_defaults = { "interface": "",
2400
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2401
":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2402
"servicename": "Mandos",
2408
"statedir": "/var/lib/mandos",
2409
"foreground": "False",
1211
server_defaults = { u"interface": u"",
1216
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1217
u"servicename": u"Mandos",
1218
u"use_dbus": u"True",
1219
u"use_ipv6": u"True",
2413
1222
# Parse config file for server-global settings
2414
1223
server_config = configparser.SafeConfigParser(server_defaults)
2415
1224
del server_defaults
2416
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1225
server_config.read(os.path.join(options.configdir,
2417
1227
# Convert the SafeConfigParser object to a dict
2418
1228
server_settings = server_config.defaults()
2419
1229
# Use the appropriate methods on the non-string config options
2420
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2421
server_settings[option] = server_config.getboolean("DEFAULT",
1230
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1231
server_settings[option] = server_config.getboolean(u"DEFAULT",
2423
1233
if server_settings["port"]:
2424
server_settings["port"] = server_config.getint("DEFAULT",
2426
if server_settings["socket"]:
2427
server_settings["socket"] = server_config.getint("DEFAULT",
2429
# Later, stdin will, and stdout and stderr might, be dup'ed
2430
# over with an opened os.devnull. But we don't want this to
2431
# happen with a supplied network socket.
2432
if 0 <= server_settings["socket"] <= 2:
2433
server_settings["socket"] = os.dup(server_settings
1234
server_settings["port"] = server_config.getint(u"DEFAULT",
2435
1236
del server_config
2437
1238
# Override the settings from the config file with command line
2438
1239
# options, if set.
2439
for option in ("interface", "address", "port", "debug",
2440
"priority", "servicename", "configdir", "use_dbus",
2441
"use_ipv6", "debuglevel", "restore", "statedir",
2442
"socket", "foreground", "zeroconf"):
1240
for option in (u"interface", u"address", u"port", u"debug",
1241
u"priority", u"servicename", u"configdir",
1242
u"use_dbus", u"use_ipv6"):
2443
1243
value = getattr(options, option)
2444
1244
if value is not None:
2445
1245
server_settings[option] = value
2447
1247
# Force all strings to be unicode
2448
1248
for option in server_settings.keys():
2449
if isinstance(server_settings[option], bytes):
2450
server_settings[option] = (server_settings[option]
2452
# Force all boolean options to be boolean
2453
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2454
"foreground", "zeroconf"):
2455
server_settings[option] = bool(server_settings[option])
2456
# Debug implies foreground
2457
if server_settings["debug"]:
2458
server_settings["foreground"] = True
1249
if type(server_settings[option]) is str:
1250
server_settings[option] = unicode(server_settings[option])
2459
1251
# Now we have our good server settings in "server_settings"
2461
1253
##################################################################
2463
if (not server_settings["zeroconf"]
2464
and not (server_settings["port"]
2465
or server_settings["socket"] != "")):
2466
parser.error("Needs port or socket to work without Zeroconf")
2468
1255
# For convenience
2469
debug = server_settings["debug"]
2470
debuglevel = server_settings["debuglevel"]
2471
use_dbus = server_settings["use_dbus"]
2472
use_ipv6 = server_settings["use_ipv6"]
2473
stored_state_path = os.path.join(server_settings["statedir"],
2475
foreground = server_settings["foreground"]
2476
zeroconf = server_settings["zeroconf"]
2479
initlogger(debug, logging.DEBUG)
2484
level = getattr(logging, debuglevel.upper())
2485
initlogger(debug, level)
2487
if server_settings["servicename"] != "Mandos":
2488
syslogger.setFormatter(
2489
logging.Formatter('Mandos ({}) [%(process)d]:'
2490
' %(levelname)s: %(message)s'.format(
2491
server_settings["servicename"])))
1256
debug = server_settings[u"debug"]
1257
use_dbus = server_settings[u"use_dbus"]
1258
use_dbus = False # XXX: Not done yet
1259
use_ipv6 = server_settings[u"use_ipv6"]
1262
syslogger.setLevel(logging.WARNING)
1263
console.setLevel(logging.WARNING)
1265
if server_settings[u"servicename"] != u"Mandos":
1266
syslogger.setFormatter(logging.Formatter
1267
(u'Mandos (%s) [%%(process)d]:'
1268
u' %%(levelname)s: %%(message)s'
1269
% server_settings[u"servicename"]))
2493
1271
# Parse config file with clients
2494
client_config = configparser.SafeConfigParser(Client
2496
client_config.read(os.path.join(server_settings["configdir"],
1272
client_defaults = { u"timeout": u"1h",
1274
u"checker": u"fping -q -- %%(host)s",
1277
client_config = configparser.SafeConfigParser(client_defaults)
1278
client_config.read(os.path.join(server_settings[u"configdir"],
2499
1281
global mandos_dbus_service
2500
1282
mandos_dbus_service = None
2503
if server_settings["socket"] != "":
2504
socketfd = server_settings["socket"]
2505
tcp_server = MandosServer(
2506
(server_settings["address"], server_settings["port"]),
2508
interface=(server_settings["interface"] or None),
2510
gnutls_priority=server_settings["priority"],
2514
pidfilename = "/run/mandos.pid"
2515
if not os.path.isdir("/run/."):
2516
pidfilename = "/var/run/mandos.pid"
2519
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2520
except IOError as e:
2521
logger.error("Could not open file %r", pidfilename,
1284
tcp_server = MandosServer((server_settings[u"address"],
1285
server_settings[u"port"]),
1287
interface=server_settings[u"interface"],
1290
server_settings[u"priority"],
1292
pidfilename = u"/var/run/mandos.pid"
1294
pidfile = open(pidfilename, u"w")
1296
logger.error(u"Could not open file %r", pidfilename)
2524
for name in ("_mandos", "mandos", "nobody"):
1299
uid = pwd.getpwnam(u"_mandos").pw_uid
1300
gid = pwd.getpwnam(u"_mandos").pw_gid
2526
uid = pwd.getpwnam(name).pw_uid
2527
gid = pwd.getpwnam(name).pw_gid
1303
uid = pwd.getpwnam(u"mandos").pw_uid
1304
gid = pwd.getpwnam(u"mandos").pw_gid
2529
1305
except KeyError:
1307
uid = pwd.getpwnam(u"nobody").pw_uid
1308
gid = pwd.getpwnam(u"nobody").pw_gid
2537
except OSError as error:
2538
if error.errno != errno.EPERM:
1315
except OSError, error:
1316
if error[0] != errno.EPERM:
1319
# Enable all possible GnuTLS debugging
2542
# Enable all possible GnuTLS debugging
2544
1321
# "Use a log level over 10 to enable all debugging options."
2545
1322
# - GnuTLS manual
2546
1323
gnutls.library.functions.gnutls_global_set_log_level(11)
2548
1325
@gnutls.library.types.gnutls_log_func
2549
1326
def debug_gnutls(level, string):
2550
logger.debug("GnuTLS: %s", string[:-1])
2552
gnutls.library.functions.gnutls_global_set_log_function(
2555
# Redirect stdin so all checkers get /dev/null
2556
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2557
os.dup2(null, sys.stdin.fileno())
2561
# Need to fork before connecting to D-Bus
2563
# Close all input and output, do double fork, etc.
2566
# multiprocessing will use threads, so before we use gobject we
2567
# need to inform gobject that threads will be used.
2568
gobject.threads_init()
1327
logger.debug(u"GnuTLS: %s", string[:-1])
1329
(gnutls.library.functions
1330
.gnutls_global_set_log_function(debug_gnutls))
2570
1332
global main_loop
2571
1333
# From the Avahi example code
2572
DBusGMainLoop(set_as_default=True)
1334
DBusGMainLoop(set_as_default=True )
2573
1335
main_loop = gobject.MainLoop()
2574
1336
bus = dbus.SystemBus()
2575
1337
# End of Avahi example code
2578
bus_name = dbus.service.BusName("se.recompile.Mandos",
2581
old_bus_name = dbus.service.BusName(
2582
"se.bsnet.fukt.Mandos", bus,
2584
except dbus.exceptions.DBusException as e:
2585
logger.error("Disabling D-Bus:", exc_info=e)
2587
server_settings["use_dbus"] = False
2588
tcp_server.use_dbus = False
2590
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2591
service = AvahiServiceToSyslog(
2592
name = server_settings["servicename"],
2593
servicetype = "_mandos._tcp",
2594
protocol = protocol,
2596
if server_settings["interface"]:
2597
service.interface = if_nametoindex(
2598
server_settings["interface"].encode("utf-8"))
2600
global multiprocessing_manager
2601
multiprocessing_manager = multiprocessing.Manager()
1339
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1340
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1341
service = AvahiService(name = server_settings[u"servicename"],
1342
servicetype = u"_mandos._tcp",
1343
protocol = protocol, bus = bus)
1344
if server_settings["interface"]:
1345
service.interface = (if_nametoindex
1346
(str(server_settings[u"interface"])))
2603
1348
client_class = Client
2605
1350
client_class = functools.partial(ClientDBus, bus = bus)
2607
client_settings = Client.config_parser(client_config)
2608
old_client_settings = {}
2611
# This is used to redirect stdout and stderr for checker processes
2613
wnull = open(os.devnull, "w") # A writable /dev/null
2614
# Only used if server is running in foreground but not in debug
2616
if debug or not foreground:
2619
# Get client data and settings from last running state.
2620
if server_settings["restore"]:
2622
with open(stored_state_path, "rb") as stored_state:
2623
clients_data, old_client_settings = pickle.load(
2625
os.remove(stored_state_path)
2626
except IOError as e:
2627
if e.errno == errno.ENOENT:
2628
logger.warning("Could not load persistent state:"
2629
" {}".format(os.strerror(e.errno)))
2631
logger.critical("Could not load persistent state:",
2634
except EOFError as e:
2635
logger.warning("Could not load persistent state: "
2639
with PGPEngine() as pgp:
2640
for client_name, client in clients_data.items():
2641
# Skip removed clients
2642
if client_name not in client_settings:
2645
# Decide which value to use after restoring saved state.
2646
# We have three different values: Old config file,
2647
# new config file, and saved state.
2648
# New config value takes precedence if it differs from old
2649
# config value, otherwise use saved state.
2650
for name, value in client_settings[client_name].items():
2652
# For each value in new config, check if it
2653
# differs from the old config value (Except for
2654
# the "secret" attribute)
2655
if (name != "secret"
2657
old_client_settings[client_name][name])):
2658
client[name] = value
2662
# Clients who has passed its expire date can still be
2663
# enabled if its last checker was successful. Clients
2664
# whose checker succeeded before we stored its state is
2665
# assumed to have successfully run all checkers during
2667
if client["enabled"]:
2668
if datetime.datetime.utcnow() >= client["expires"]:
2669
if not client["last_checked_ok"]:
2671
"disabling client {} - Client never "
2672
"performed a successful checker".format(
2674
client["enabled"] = False
2675
elif client["last_checker_status"] != 0:
2677
"disabling client {} - Client last"
2678
" checker failed with error code"
2681
client["last_checker_status"]))
2682
client["enabled"] = False
2684
client["expires"] = (
2685
datetime.datetime.utcnow()
2686
+ client["timeout"])
2687
logger.debug("Last checker succeeded,"
2688
" keeping {} enabled".format(
2691
client["secret"] = pgp.decrypt(
2692
client["encrypted_secret"],
2693
client_settings[client_name]["secret"])
2695
# If decryption fails, we use secret from new settings
2696
logger.debug("Failed to decrypt {} old secret".format(
2698
client["secret"] = (client_settings[client_name]
2701
# Add/remove clients based on new changes made to config
2702
for client_name in (set(old_client_settings)
2703
- set(client_settings)):
2704
del clients_data[client_name]
2705
for client_name in (set(client_settings)
2706
- set(old_client_settings)):
2707
clients_data[client_name] = client_settings[client_name]
2709
# Create all client objects
2710
for client_name, client in clients_data.items():
2711
tcp_server.clients[client_name] = client_class(
2714
server_settings = server_settings)
1351
tcp_server.clients.update(set(
1352
client_class(name = section,
1353
config= dict(client_config.items(section)))
1354
for section in client_config.sections()))
2716
1355
if not tcp_server.clients:
2717
logger.warning("No clients defined")
2720
if pidfile is not None:
1356
logger.warning(u"No clients defined")
1359
# Redirect stdin so all checkers get /dev/null
1360
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1361
os.dup2(null, sys.stdin.fileno())
1365
# No console logging
1366
logger.removeHandler(console)
1367
# Close all input and output, do double fork, etc.
1371
with closing(pidfile):
2721
1372
pid = os.getpid()
2724
print(pid, file=pidfile)
2726
logger.error("Could not write to file %r with PID %d",
1373
pidfile.write(str(pid) + "\n")
1376
logger.error(u"Could not write to file %r with PID %d",
1379
# "pidfile" was never created
1384
"Cleanup function; run on exit"
1387
while tcp_server.clients:
1388
client = tcp_server.clients.pop()
1389
client.disable_hook = None
1392
atexit.register(cleanup)
1395
signal.signal(signal.SIGINT, signal.SIG_IGN)
2731
1396
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2732
1397
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2736
@alternate_dbus_interfaces(
2737
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2738
class MandosDBusService(DBusObjectWithProperties):
1400
class MandosDBusService(dbus.service.Object):
2739
1401
"""A D-Bus proxy object"""
2741
1402
def __init__(self):
2742
dbus.service.Object.__init__(self, bus, "/")
2744
_interface = "se.recompile.Mandos"
2746
@dbus_interface_annotations(_interface)
2749
"org.freedesktop.DBus.Property.EmitsChangedSignal":
2752
@dbus.service.signal(_interface, signature="o")
2753
def ClientAdded(self, objpath):
2757
@dbus.service.signal(_interface, signature="ss")
2758
def ClientNotFound(self, fingerprint, address):
2762
@dbus.service.signal(_interface, signature="os")
1403
dbus.service.Object.__init__(self, bus, u"/")
1404
_interface = u"se.bsnet.fukt.Mandos"
1406
@dbus.service.signal(_interface, signature=u"oa{sv}")
1407
def ClientAdded(self, objpath, properties):
1411
@dbus.service.signal(_interface, signature=u"s")
1412
def ClientNotFound(self, fingerprint):
1416
@dbus.service.signal(_interface, signature=u"os")
2763
1417
def ClientRemoved(self, objpath, name):
2767
@dbus.service.method(_interface, out_signature="ao")
1421
@dbus.service.method(_interface, out_signature=u"ao")
2768
1422
def GetAllClients(self):
2770
return dbus.Array(c.dbus_object_path for c in
2771
tcp_server.clients.itervalues())
1424
return dbus.Array(c.dbus_object_path
1425
for c in tcp_server.clients)
2773
1427
@dbus.service.method(_interface,
2774
out_signature="a{oa{sv}}")
1428
out_signature=u"a{oa{sv}}")
2775
1429
def GetAllClientsWithProperties(self):
2777
1431
return dbus.Dictionary(
2778
{ c.dbus_object_path: c.GetAll("")
2779
for c in tcp_server.clients.itervalues() },
1432
((c.dbus_object_path, c.GetAllProperties())
1433
for c in tcp_server.clients),
1434
signature=u"oa{sv}")
2782
@dbus.service.method(_interface, in_signature="o")
1436
@dbus.service.method(_interface, in_signature=u"o")
2783
1437
def RemoveClient(self, object_path):
2785
for c in tcp_server.clients.itervalues():
1439
for c in tcp_server.clients:
2786
1440
if c.dbus_object_path == object_path:
2787
del tcp_server.clients[c.name]
1441
tcp_server.clients.remove(c)
2788
1442
c.remove_from_connection()
2789
1443
# Don't signal anything except ClientRemoved
2790
c.disable(quiet=True)
1444
c.disable(signal=False)
2791
1445
# Emit D-Bus signal
2792
1446
self.ClientRemoved(object_path, c.name)
2794
raise KeyError(object_path)
2798
1452
mandos_dbus_service = MandosDBusService()
2801
"Cleanup function; run on exit"
2805
multiprocessing.active_children()
2807
if not (tcp_server.clients or client_settings):
2810
# Store client before exiting. Secrets are encrypted with key
2811
# based on what config file has. If config file is
2812
# removed/edited, old secret will thus be unrecovable.
2814
with PGPEngine() as pgp:
2815
for client in tcp_server.clients.itervalues():
2816
key = client_settings[client.name]["secret"]
2817
client.encrypted_secret = pgp.encrypt(client.secret,
2821
# A list of attributes that can not be pickled
2823
exclude = { "bus", "changedstate", "secret",
2824
"checker", "server_settings" }
2825
for name, typ in inspect.getmembers(dbus.service
2829
client_dict["encrypted_secret"] = (client
2831
for attr in client.client_structure:
2832
if attr not in exclude:
2833
client_dict[attr] = getattr(client, attr)
2835
clients[client.name] = client_dict
2836
del client_settings[client.name]["secret"]
2839
with tempfile.NamedTemporaryFile(
2843
dir=os.path.dirname(stored_state_path),
2844
delete=False) as stored_state:
2845
pickle.dump((clients, client_settings), stored_state)
2846
tempname = stored_state.name
2847
os.rename(tempname, stored_state_path)
2848
except (IOError, OSError) as e:
2854
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2855
logger.warning("Could not save persistent state: {}"
2856
.format(os.strerror(e.errno)))
2858
logger.warning("Could not save persistent state:",
2862
# Delete all clients, and settings from config
2863
while tcp_server.clients:
2864
name, client = tcp_server.clients.popitem()
2866
client.remove_from_connection()
2867
# Don't signal anything except ClientRemoved
2868
client.disable(quiet=True)
2871
mandos_dbus_service.ClientRemoved(
2872
client.dbus_object_path, client.name)
2873
client_settings.clear()
2875
atexit.register(cleanup)
2877
for client in tcp_server.clients.itervalues():
1454
for client in tcp_server.clients:
2879
1456
# Emit D-Bus signal
2880
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2881
# Need to initiate checking of clients
2883
client.init_checker()
1457
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1458
client.GetAllProperties())
2885
1461
tcp_server.enable()
2886
1462
tcp_server.server_activate()
2888
1464
# Find out what port we got
2890
service.port = tcp_server.socket.getsockname()[1]
1465
service.port = tcp_server.socket.getsockname()[1]
2892
logger.info("Now listening on address %r, port %d,"
2893
" flowinfo %d, scope_id %d",
2894
*tcp_server.socket.getsockname())
1467
logger.info(u"Now listening on address %r, port %d,"
1468
" flowinfo %d, scope_id %d"
1469
% tcp_server.socket.getsockname())
2896
logger.info("Now listening on address %r, port %d",
2897
*tcp_server.socket.getsockname())
1471
logger.info(u"Now listening on address %r, port %d"
1472
% tcp_server.socket.getsockname())
2899
1474
#service.interface = tcp_server.socket.getsockname()[3]
2903
# From the Avahi example code
2906
except dbus.exceptions.DBusException as error:
2907
logger.critical("D-Bus Exception", exc_info=error)
2910
# End of Avahi example code
1477
# From the Avahi example code
1480
except dbus.exceptions.DBusException, error:
1481
logger.critical(u"DBusException: %s", error)
1483
# End of Avahi example code
2912
1485
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2913
1486
lambda *args, **kwargs:
2914
1487
(tcp_server.handle_request
2915
1488
(*args[2:], **kwargs) or True))
2917
logger.debug("Starting main loop")
1490
logger.debug(u"Starting main loop")
2918
1491
main_loop.run()
2919
except AvahiError as error:
2920
logger.critical("Avahi Error", exc_info=error)
1492
except AvahiError, error:
1493
logger.critical(u"AvahiError: %s", error)
2923
1495
except KeyboardInterrupt:
2925
print("", file=sys.stderr)
2926
logger.debug("Server received KeyboardInterrupt")
2927
logger.debug("Server exiting")
2928
# Must run before the D-Bus bus name gets deregistered
1498
logger.debug(u"Server received KeyboardInterrupt")
1499
logger.debug(u"Server exiting")
2932
1501
if __name__ == '__main__':