352
190
dbus.UInt16(self.port),
353
191
avahi.string_array_to_txt_array(self.TXT))
354
192
self.group.Commit()
356
193
def entry_group_state_changed(self, state, error):
357
194
"""Derived from the Avahi example code"""
358
logger.debug("Avahi entry group state change: %i", state)
195
logger.debug(u"Avahi state change: %i", state)
360
197
if state == avahi.ENTRY_GROUP_ESTABLISHED:
361
logger.debug("Zeroconf service established.")
198
logger.debug(u"Zeroconf service established.")
362
199
elif state == avahi.ENTRY_GROUP_COLLISION:
363
logger.info("Zeroconf service name collision.")
200
logger.warning(u"Zeroconf service name collision.")
365
202
elif state == avahi.ENTRY_GROUP_FAILURE:
366
logger.critical("Avahi: Error in group state changed %s",
368
raise AvahiGroupError("State changed: {!s}".format(error))
203
logger.critical(u"Avahi: Error in group state changed %s",
205
raise AvahiGroupError(u"State changed: %s"
370
207
def cleanup(self):
371
208
"""Derived from the Avahi example code"""
372
209
if self.group is not None:
375
except (dbus.exceptions.UnknownMethodException,
376
dbus.exceptions.DBusException):
378
211
self.group = None
381
def server_state_changed(self, state, error=None):
212
def server_state_changed(self, state):
382
213
"""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)
214
if state == avahi.SERVER_COLLISION:
215
logger.error(u"Zeroconf server name collision")
397
217
elif state == avahi.SERVER_RUNNING:
400
except dbus.exceptions.DBusException as error:
401
if (error.get_dbus_name()
402
== "org.freedesktop.Avahi.CollisionError"):
403
logger.info("Local Zeroconf service name"
405
return self.rename(remove=False)
407
logger.critical("D-Bus Exception", exc_info=error)
412
logger.debug("Unknown state: %r", state)
414
logger.debug("Unknown state: %r: %r", state, error)
416
219
def activate(self):
417
220
"""Derived from the Avahi example code"""
418
221
if self.server is None:
419
222
self.server = dbus.Interface(
420
223
self.bus.get_object(avahi.DBUS_NAME,
421
avahi.DBUS_PATH_SERVER,
422
follow_name_owner_changes=True),
224
avahi.DBUS_PATH_SERVER),
423
225
avahi.DBUS_INTERFACE_SERVER)
424
self.server.connect_to_signal("StateChanged",
425
self.server_state_changed)
226
self.server.connect_to_signal(u"StateChanged",
227
self.server_state_changed)
426
228
self.server_state_changed(self.server.GetState())
429
class AvahiServiceToSyslog(AvahiService):
430
def rename(self, *args, **kwargs):
431
"""Add the new name to the syslog messages"""
432
ret = AvahiService.rename(self, *args, **kwargs)
433
syslogger.setFormatter(logging.Formatter(
434
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
438
def call_pipe(connection, # : multiprocessing.Connection
439
func, *args, **kwargs):
440
"""This function is meant to be called by multiprocessing.Process
442
This function runs func(*args, **kwargs), and writes the resulting
443
return value on the provided multiprocessing.Connection.
445
connection.send(func(*args, **kwargs))
448
231
class Client(object):
449
232
"""A representation of a client host served by this server.
452
approved: bool(); 'None' if not yet approved/disapproved
453
approval_delay: datetime.timedelta(); Time to wait for approval
454
approval_duration: datetime.timedelta(); Duration of one approval
235
name: string; from the config file, used in log messages and
237
fingerprint: string (40 or 32 hexadecimal digits); used to
238
uniquely identify the client
239
secret: bytestring; sent verbatim (over TLS) to client
240
host: string; available for use by the checker command
241
created: datetime.datetime(); (UTC) object creation
242
last_enabled: datetime.datetime(); (UTC)
244
last_checked_ok: datetime.datetime(); (UTC) or None
245
timeout: datetime.timedelta(); How long from last_checked_ok
246
until this client is disabled
247
interval: datetime.timedelta(); How often to start a new checker
248
disable_hook: If set, called by disable() as disable_hook(self)
455
249
checker: subprocess.Popen(); a running checker process used
456
250
to see if the client lives.
457
251
'None' if no process is running.
458
checker_callback_tag: a gobject event source tag, or None
459
checker_command: string; External command which is run to check
460
if client lives. %() expansions are done at
252
checker_initiator_tag: a gobject event source tag, or None
253
disable_initiator_tag: - '' -
254
checker_callback_tag: - '' -
255
checker_command: string; External command which is run to check if
256
client lives. %() expansions are done at
461
257
runtime with vars(self) as dict, so that for
462
258
instance %(name)s can be used in the command.
463
checker_initiator_tag: a gobject event source tag, or None
464
created: datetime.datetime(); (UTC) object creation
465
client_structure: Object describing what attributes a client has
466
and is used for storing the client at exit
467
259
current_checker_command: string; current running checker_command
468
disable_initiator_tag: a gobject event source tag, or None
470
fingerprint: string (40 or 32 hexadecimal digits); used to
471
uniquely identify the client
472
host: string; available for use by the checker command
473
interval: datetime.timedelta(); How often to start a new checker
474
last_approval_request: datetime.datetime(); (UTC) or None
475
last_checked_ok: datetime.datetime(); (UTC) or None
476
last_checker_status: integer between 0 and 255 reflecting exit
477
status of last checker. -1 reflects crashed
478
checker, -2 means no checker completed yet.
479
last_checker_signal: The signal which killed the last checker, if
480
last_checker_status is -1
481
last_enabled: datetime.datetime(); (UTC) or None
482
name: string; from the config file, used in log messages and
484
secret: bytestring; sent verbatim (over TLS) to client
485
timeout: datetime.timedelta(); How long from last_checked_ok
486
until this client is disabled
487
extended_timeout: extra long timeout when secret has been sent
488
runtime_expansions: Allowed attributes for runtime expansion.
489
expires: datetime.datetime(); time (UTC) when a client will be
491
server_settings: The server_settings dict from main()
494
runtime_expansions = ("approval_delay", "approval_duration",
495
"created", "enabled", "expires",
496
"fingerprint", "host", "interval",
497
"last_approval_request", "last_checked_ok",
498
"last_enabled", "name", "timeout")
501
"extended_timeout": "PT15M",
503
"checker": "fping -q -- %%(host)s",
505
"approval_delay": "PT0S",
506
"approval_duration": "PT1S",
507
"approved_by_default": "True",
512
def config_parser(config):
513
"""Construct a new dict of client settings of this form:
514
{ client_name: {setting_name: value, ...}, ...}
515
with exceptions for any special settings as defined above.
516
NOTE: Must be a pure function. Must return the same result
517
value given the same arguments.
520
for client_name in config.sections():
521
section = dict(config.items(client_name))
522
client = settings[client_name] = {}
524
client["host"] = section["host"]
525
# Reformat values from string types to Python types
526
client["approved_by_default"] = config.getboolean(
527
client_name, "approved_by_default")
528
client["enabled"] = config.getboolean(client_name,
531
# Uppercase and remove spaces from fingerprint for later
532
# comparison purposes with return value from the
533
# fingerprint() function
534
client["fingerprint"] = (section["fingerprint"].upper()
536
if "secret" in section:
537
client["secret"] = section["secret"].decode("base64")
538
elif "secfile" in section:
539
with open(os.path.expanduser(os.path.expandvars
540
(section["secfile"])),
542
client["secret"] = secfile.read()
544
raise TypeError("No secret or secfile for section {}"
546
client["timeout"] = string_to_delta(section["timeout"])
547
client["extended_timeout"] = string_to_delta(
548
section["extended_timeout"])
549
client["interval"] = string_to_delta(section["interval"])
550
client["approval_delay"] = string_to_delta(
551
section["approval_delay"])
552
client["approval_duration"] = string_to_delta(
553
section["approval_duration"])
554
client["checker_command"] = section["checker"]
555
client["last_approval_request"] = None
556
client["last_checked_ok"] = None
557
client["last_checker_status"] = -2
561
def __init__(self, settings, name = None, server_settings=None):
263
def _timedelta_to_milliseconds(td):
264
"Convert a datetime.timedelta() to milliseconds"
265
return ((td.days * 24 * 60 * 60 * 1000)
266
+ (td.seconds * 1000)
267
+ (td.microseconds // 1000))
269
def timeout_milliseconds(self):
270
"Return the 'timeout' attribute in milliseconds"
271
return self._timedelta_to_milliseconds(self.timeout)
273
def interval_milliseconds(self):
274
"Return the 'interval' attribute in milliseconds"
275
return self._timedelta_to_milliseconds(self.interval)
277
def __init__(self, name = None, disable_hook=None, config=None):
278
"""Note: the 'checker' key in 'config' sets the
279
'checker_command' attribute and *not* the 'checker'
563
if server_settings is None:
565
self.server_settings = server_settings
566
# adding all client settings
567
for setting, value in settings.items():
568
setattr(self, setting, value)
571
if not hasattr(self, "last_enabled"):
572
self.last_enabled = datetime.datetime.utcnow()
573
if not hasattr(self, "expires"):
574
self.expires = (datetime.datetime.utcnow()
284
logger.debug(u"Creating client %r", self.name)
285
# Uppercase and remove spaces from fingerprint for later
286
# comparison purposes with return value from the fingerprint()
288
self.fingerprint = (config[u"fingerprint"].upper()
290
logger.debug(u" Fingerprint: %s", self.fingerprint)
291
if u"secret" in config:
292
self.secret = config[u"secret"].decode(u"base64")
293
elif u"secfile" in config:
294
with open(os.path.expanduser(os.path.expandvars
295
(config[u"secfile"])),
297
self.secret = secfile.read()
577
self.last_enabled = None
580
logger.debug("Creating client %r", self.name)
581
logger.debug(" Fingerprint: %s", self.fingerprint)
582
self.created = settings.get("created",
583
datetime.datetime.utcnow())
585
# attributes specific for this server instance
299
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
302
self.created = datetime.datetime.utcnow()
304
self.last_enabled = None
305
self.last_checked_ok = None
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
308
self.disable_hook = disable_hook
586
309
self.checker = None
587
310
self.checker_initiator_tag = None
588
311
self.disable_initiator_tag = None
589
312
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
590
314
self.current_checker_command = None
592
self.approvals_pending = 0
593
self.changedstate = multiprocessing_manager.Condition(
594
multiprocessing_manager.Lock())
595
self.client_structure = [attr
596
for attr in self.__dict__.iterkeys()
597
if not attr.startswith("_")]
598
self.client_structure.append("client_structure")
600
for name, t in inspect.getmembers(
601
type(self), lambda obj: isinstance(obj, property)):
602
if not name.startswith("_"):
603
self.client_structure.append(name)
605
# Send notice to process children that client state has changed
606
def send_changedstate(self):
607
with self.changedstate:
608
self.changedstate.notify_all()
315
self.last_connect = None
610
317
def enable(self):
611
318
"""Start this client's checker and timeout hooks"""
612
if getattr(self, "enabled", False):
319
if getattr(self, u"enabled", False):
613
320
# Already enabled
615
self.expires = datetime.datetime.utcnow() + self.timeout
617
322
self.last_enabled = datetime.datetime.utcnow()
619
self.send_changedstate()
323
# Schedule a new checker to be started an 'interval' from now,
324
# and every interval from then on.
325
self.checker_initiator_tag = (gobject.timeout_add
326
(self.interval_milliseconds(),
328
# Schedule a disable() when 'timeout' has passed
329
self.disable_initiator_tag = (gobject.timeout_add
330
(self.timeout_milliseconds(),
333
# Also start a new checker *right now*.
621
336
def disable(self, quiet=True):
622
337
"""Disable this client."""
623
338
if not getattr(self, "enabled", False):
626
logger.info("Disabling client %s", self.name)
627
if getattr(self, "disable_initiator_tag", None) is not None:
341
logger.info(u"Disabling client %s", self.name)
342
if getattr(self, u"disable_initiator_tag", False):
628
343
gobject.source_remove(self.disable_initiator_tag)
629
344
self.disable_initiator_tag = None
631
if getattr(self, "checker_initiator_tag", None) is not None:
345
if getattr(self, u"checker_initiator_tag", False):
632
346
gobject.source_remove(self.checker_initiator_tag)
633
347
self.checker_initiator_tag = None
634
348
self.stop_checker()
349
if self.disable_hook:
350
self.disable_hook(self)
635
351
self.enabled = False
637
self.send_changedstate()
638
352
# Do not run this again if called by a gobject.timeout_add
641
355
def __del__(self):
356
self.disable_hook = None
644
def init_checker(self):
645
# Schedule a new checker to be started an 'interval' from now,
646
# and every interval from then on.
647
if self.checker_initiator_tag is not None:
648
gobject.source_remove(self.checker_initiator_tag)
649
self.checker_initiator_tag = gobject.timeout_add(
650
int(self.interval.total_seconds() * 1000),
652
# Schedule a disable() when 'timeout' has passed
653
if self.disable_initiator_tag is not None:
654
gobject.source_remove(self.disable_initiator_tag)
655
self.disable_initiator_tag = gobject.timeout_add(
656
int(self.timeout.total_seconds() * 1000), self.disable)
657
# Also start a new checker *right now*.
660
def checker_callback(self, source, condition, connection,
359
def checker_callback(self, pid, condition, command):
662
360
"""The checker has completed, so take appropriate actions."""
663
361
self.checker_callback_tag = None
664
362
self.checker = None
665
# Read return code from connection (see call_pipe)
666
returncode = connection.recv()
670
self.last_checker_status = returncode
671
self.last_checker_signal = None
672
if self.last_checker_status == 0:
673
logger.info("Checker for %(name)s succeeded",
363
if os.WIFEXITED(condition):
364
exitstatus = os.WEXITSTATUS(condition)
366
logger.info(u"Checker for %(name)s succeeded",
675
368
self.checked_ok()
677
logger.info("Checker for %(name)s failed", vars(self))
370
logger.info(u"Checker for %(name)s failed",
679
self.last_checker_status = -1
680
self.last_checker_signal = -returncode
681
logger.warning("Checker for %(name)s crashed?",
373
logger.warning(u"Checker for %(name)s crashed?",
685
376
def checked_ok(self):
686
"""Assert that the client has been seen, alive and well."""
377
"""Bump up the timeout for this client.
379
This should only be called when the client has been seen,
687
382
self.last_checked_ok = datetime.datetime.utcnow()
688
self.last_checker_status = 0
689
self.last_checker_signal = None
692
def bump_timeout(self, timeout=None):
693
"""Bump up the timeout for this client."""
695
timeout = self.timeout
696
if self.disable_initiator_tag is not None:
697
gobject.source_remove(self.disable_initiator_tag)
698
self.disable_initiator_tag = None
699
if getattr(self, "enabled", False):
700
self.disable_initiator_tag = gobject.timeout_add(
701
int(timeout.total_seconds() * 1000), self.disable)
702
self.expires = datetime.datetime.utcnow() + timeout
704
def need_approval(self):
705
self.last_approval_request = datetime.datetime.utcnow()
383
gobject.source_remove(self.disable_initiator_tag)
384
self.disable_initiator_tag = (gobject.timeout_add
385
(self.timeout_milliseconds(),
707
388
def start_checker(self):
708
389
"""Start a new checker subprocess if one is not running.
874
class DBusObjectWithAnnotations(dbus.service.Object):
875
"""A D-Bus object with annotations.
877
Classes inheriting from this can use the dbus_annotations
878
decorator to add annotations to methods or signals.
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
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
902
path_keyword = 'object_path',
903
connection_keyword = 'connection')
904
def Introspect(self, object_path, connection):
905
"""Overloading of standard D-Bus method.
907
Inserts annotation tags on methods and signals.
909
xmlstring = dbus.service.Object.Introspect(self, object_path,
912
document = xml.dom.minidom.parseString(xmlstring)
914
for if_tag in document.getElementsByTagName("interface"):
915
# Add annotation tags
916
for typ in ("method", "signal"):
917
for tag in if_tag.getElementsByTagName(typ):
919
for name, prop in (self.
920
_get_all_dbus_things(typ)):
921
if (name == tag.getAttribute("name")
922
and prop._dbus_interface
923
== if_tag.getAttribute("name")):
924
annots.update(getattr(
925
prop, "_dbus_annotations", {}))
926
for name, value in annots.items():
927
ann_tag = document.createElement(
929
ann_tag.setAttribute("name", name)
930
ann_tag.setAttribute("value", value)
931
tag.appendChild(ann_tag)
932
# Add interface annotation tags
933
for annotation, value in dict(
934
itertools.chain.from_iterable(
935
annotations().items()
936
for name, annotations
937
in self._get_all_dbus_things("interface")
938
if name == if_tag.getAttribute("name")
940
ann_tag = document.createElement("annotation")
941
ann_tag.setAttribute("name", annotation)
942
ann_tag.setAttribute("value", value)
943
if_tag.appendChild(ann_tag)
944
# Fix argument name for the Introspect method itself
945
if (if_tag.getAttribute("name")
946
== dbus.INTROSPECTABLE_IFACE):
947
for cn in if_tag.getElementsByTagName("method"):
948
if cn.getAttribute("name") == "Introspect":
949
for arg in cn.getElementsByTagName("arg"):
950
if (arg.getAttribute("direction")
952
arg.setAttribute("name",
954
xmlstring = document.toxml("utf-8")
956
except (AttributeError, xml.dom.DOMException,
957
xml.parsers.expat.ExpatError) as error:
958
logger.error("Failed to override Introspection method",
963
class DBusObjectWithProperties(DBusObjectWithAnnotations):
529
class DBusObjectWithProperties(dbus.service.Object):
964
530
"""A D-Bus object with properties.
966
532
Classes inheriting from this can use the dbus_service_property
967
533
decorator to expose methods as D-Bus properties. It exposes the
968
534
standard Get(), Set(), and GetAll() methods on the D-Bus.
538
def _is_dbus_property(obj):
539
return getattr(obj, u"_dbus_is_property", False)
541
def _get_all_dbus_properties(self):
542
"""Returns a generator of (name, attribute) pairs
544
return ((prop._dbus_name, prop)
546
inspect.getmembers(self, self._is_dbus_property))
971
548
def _get_dbus_property(self, interface_name, property_name):
972
549
"""Returns a bound method if one exists which is a D-Bus
973
550
property with the specified name and interface.
975
for cls in self.__class__.__mro__:
976
for name, value in inspect.getmembers(
977
cls, self._is_dbus_thing("property")):
978
if (value._dbus_name == property_name
979
and value._dbus_interface == interface_name):
980
return value.__get__(self)
552
for name in (property_name,
553
property_name + u"_dbus_property"):
554
prop = getattr(self, name, None)
556
or not self._is_dbus_property(prop)
557
or prop._dbus_name != property_name
558
or (interface_name and prop._dbus_interface
559
and interface_name != prop._dbus_interface)):
982
562
# No such property
983
raise DBusPropertyNotFound("{}:{}.{}".format(
984
self.dbus_object_path, interface_name, property_name))
987
def _get_all_interface_names(cls):
988
"""Get a sequence of all interfaces supported by an object"""
989
return (name for name in set(getattr(getattr(x, attr),
990
"_dbus_interface", None)
991
for x in (inspect.getmro(cls))
995
@dbus.service.method(dbus.PROPERTIES_IFACE,
563
raise DBusPropertyNotFound(self.dbus_object_path + u":"
564
+ interface_name + u"."
567
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
998
569
def Get(self, interface_name, property_name):
999
570
"""Standard D-Bus property Get() method, see D-Bus standard.
1001
572
prop = self._get_dbus_property(interface_name, property_name)
1002
if prop._dbus_access == "write":
573
if prop._dbus_access == u"write":
1003
574
raise DBusPropertyAccessException(property_name)
1005
if not hasattr(value, "variant_level"):
576
if not hasattr(value, u"variant_level"):
1007
578
return type(value)(value, variant_level=value.variant_level+1)
1009
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
580
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
1010
581
def Set(self, interface_name, property_name, value):
1011
582
"""Standard D-Bus property Set() method, see D-Bus standard.
1013
584
prop = self._get_dbus_property(interface_name, property_name)
1014
if prop._dbus_access == "read":
585
if prop._dbus_access == u"read":
1015
586
raise DBusPropertyAccessException(property_name)
1016
if prop._dbus_get_args_options["byte_arrays"]:
587
if prop._dbus_get_args_options[u"byte_arrays"]:
1017
588
# The byte_arrays option is not supported yet on
1018
589
# signatures other than "ay".
1019
if prop._dbus_signature != "ay":
1020
raise ValueError("Byte arrays not supported for non-"
1021
"'ay' signature {!r}"
1022
.format(prop._dbus_signature))
1023
value = dbus.ByteArray(b''.join(chr(byte)
590
if prop._dbus_signature != u"ay":
592
value = dbus.ByteArray(''.join(unichr(byte)
1027
@dbus.service.method(dbus.PROPERTIES_IFACE,
1029
out_signature="a{sv}")
596
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
out_signature=u"a{sv}")
1030
598
def GetAll(self, interface_name):
1031
599
"""Standard D-Bus property GetAll() method, see D-Bus
1034
602
Note: Will not include properties with access="write".
1037
for name, prop in self._get_all_dbus_things("property"):
605
for name, prop in self._get_all_dbus_properties():
1038
606
if (interface_name
1039
607
and interface_name != prop._dbus_interface):
1040
608
# Interface non-empty but did not match
1042
610
# Ignore write-only properties
1043
if prop._dbus_access == "write":
611
if prop._dbus_access == u"write":
1046
if not hasattr(value, "variant_level"):
1047
properties[name] = value
614
if not hasattr(value, u"variant_level"):
1049
properties[name] = type(value)(
1050
value, variant_level = value.variant_level + 1)
1051
return dbus.Dictionary(properties, signature="sv")
1053
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
1054
def PropertiesChanged(self, interface_name, changed_properties,
1055
invalidated_properties):
1056
"""Standard D-Bus PropertiesChanged() signal, see D-Bus
617
all[name] = type(value)(value, variant_level=
618
value.variant_level+1)
619
return dbus.Dictionary(all, signature=u"sv")
1061
621
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1063
623
path_keyword='object_path',
1064
624
connection_keyword='connection')
1065
625
def Introspect(self, object_path, connection):
1066
"""Overloading of standard D-Bus method.
1068
Inserts property tags and interface annotation tags.
626
"""Standard D-Bus method, overloaded to insert property tags.
1070
xmlstring = DBusObjectWithAnnotations.Introspect(self,
628
xmlstring = dbus.service.Object.Introspect(self, object_path,
1074
631
document = xml.dom.minidom.parseString(xmlstring)
1076
632
def make_tag(document, name, prop):
1077
e = document.createElement("property")
1078
e.setAttribute("name", name)
1079
e.setAttribute("type", prop._dbus_signature)
1080
e.setAttribute("access", prop._dbus_access)
633
e = document.createElement(u"property")
634
e.setAttribute(u"name", name)
635
e.setAttribute(u"type", prop._dbus_signature)
636
e.setAttribute(u"access", prop._dbus_access)
1083
for if_tag in document.getElementsByTagName("interface"):
638
for if_tag in document.getElementsByTagName(u"interface"):
1085
639
for tag in (make_tag(document, name, prop)
1087
in self._get_all_dbus_things("property")
641
in self._get_all_dbus_properties()
1088
642
if prop._dbus_interface
1089
== if_tag.getAttribute("name")):
643
== if_tag.getAttribute(u"name")):
1090
644
if_tag.appendChild(tag)
1091
# Add annotation tags for properties
1092
for tag in if_tag.getElementsByTagName("property"):
1094
for name, prop in self._get_all_dbus_things(
1096
if (name == tag.getAttribute("name")
1097
and prop._dbus_interface
1098
== if_tag.getAttribute("name")):
1099
annots.update(getattr(
1100
prop, "_dbus_annotations", {}))
1101
for name, value in annots.items():
1102
ann_tag = document.createElement(
1104
ann_tag.setAttribute("name", name)
1105
ann_tag.setAttribute("value", value)
1106
tag.appendChild(ann_tag)
1107
645
# Add the names to the return values for the
1108
646
# "org.freedesktop.DBus.Properties" methods
1109
if (if_tag.getAttribute("name")
1110
== "org.freedesktop.DBus.Properties"):
1111
for cn in if_tag.getElementsByTagName("method"):
1112
if cn.getAttribute("name") == "Get":
1113
for arg in cn.getElementsByTagName("arg"):
1114
if (arg.getAttribute("direction")
1116
arg.setAttribute("name", "value")
1117
elif cn.getAttribute("name") == "GetAll":
1118
for arg in cn.getElementsByTagName("arg"):
1119
if (arg.getAttribute("direction")
1121
arg.setAttribute("name", "props")
1122
xmlstring = document.toxml("utf-8")
1124
except (AttributeError, xml.dom.DOMException,
1125
xml.parsers.expat.ExpatError) as error:
1126
logger.error("Failed to override Introspection method",
1131
dbus.OBJECT_MANAGER_IFACE
1132
except AttributeError:
1133
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
1135
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
1136
"""A D-Bus object with an ObjectManager.
1138
Classes inheriting from this exposes the standard
1139
GetManagedObjects call and the InterfacesAdded and
1140
InterfacesRemoved signals on the standard
1141
"org.freedesktop.DBus.ObjectManager" interface.
1143
Note: No signals are sent automatically; they must be sent
1146
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
1147
out_signature = "a{oa{sa{sv}}}")
1148
def GetManagedObjects(self):
1149
"""This function must be overridden"""
1150
raise NotImplementedError()
1152
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
1153
signature = "oa{sa{sv}}")
1154
def InterfacesAdded(self, object_path, interfaces_and_properties):
1157
@dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
1158
def InterfacesRemoved(self, object_path, interfaces):
1161
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1162
out_signature = "s",
1163
path_keyword = 'object_path',
1164
connection_keyword = 'connection')
1165
def Introspect(self, object_path, connection):
1166
"""Overloading of standard D-Bus method.
1168
Override return argument name of GetManagedObjects to be
1169
"objpath_interfaces_and_properties"
1171
xmlstring = DBusObjectWithAnnotations.Introspect(self,
1175
document = xml.dom.minidom.parseString(xmlstring)
1177
for if_tag in document.getElementsByTagName("interface"):
1178
# Fix argument name for the GetManagedObjects method
1179
if (if_tag.getAttribute("name")
1180
== dbus.OBJECT_MANAGER_IFACE):
1181
for cn in if_tag.getElementsByTagName("method"):
1182
if (cn.getAttribute("name")
1183
== "GetManagedObjects"):
1184
for arg in cn.getElementsByTagName("arg"):
1185
if (arg.getAttribute("direction")
1189
"objpath_interfaces"
1191
xmlstring = document.toxml("utf-8")
1193
except (AttributeError, xml.dom.DOMException,
1194
xml.parsers.expat.ExpatError) as error:
1195
logger.error("Failed to override Introspection method",
1199
def datetime_to_dbus(dt, variant_level=0):
1200
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1202
return dbus.String("", variant_level = variant_level)
1203
return dbus.String(dt.isoformat(), variant_level=variant_level)
1206
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1207
"""A class decorator; applied to a subclass of
1208
dbus.service.Object, it will add alternate D-Bus attributes with
1209
interface names according to the "alt_interface_names" mapping.
1212
@alternate_dbus_interfaces({"org.example.Interface":
1213
"net.example.AlternateInterface"})
1214
class SampleDBusObject(dbus.service.Object):
1215
@dbus.service.method("org.example.Interface")
1216
def SampleDBusMethod():
1219
The above "SampleDBusMethod" on "SampleDBusObject" will be
1220
reachable via two interfaces: "org.example.Interface" and
1221
"net.example.AlternateInterface", the latter of which will have
1222
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1223
"true", unless "deprecate" is passed with a False value.
1225
This works for methods and signals, and also for D-Bus properties
1226
(from DBusObjectWithProperties) and interfaces (from the
1227
dbus_interface_annotations decorator).
1231
for orig_interface_name, alt_interface_name in (
1232
alt_interface_names.items()):
1234
interface_names = set()
1235
# Go though all attributes of the class
1236
for attrname, attribute in inspect.getmembers(cls):
1237
# Ignore non-D-Bus attributes, and D-Bus attributes
1238
# with the wrong interface name
1239
if (not hasattr(attribute, "_dbus_interface")
1240
or not attribute._dbus_interface.startswith(
1241
orig_interface_name)):
1243
# Create an alternate D-Bus interface name based on
1245
alt_interface = attribute._dbus_interface.replace(
1246
orig_interface_name, alt_interface_name)
1247
interface_names.add(alt_interface)
1248
# Is this a D-Bus signal?
1249
if getattr(attribute, "_dbus_is_signal", False):
1250
if sys.version_info.major == 2:
1251
# Extract the original non-method undecorated
1252
# function by black magic
1253
nonmethod_func = (dict(
1254
zip(attribute.func_code.co_freevars,
1255
attribute.__closure__))
1256
["func"].cell_contents)
1258
nonmethod_func = attribute
1259
# Create a new, but exactly alike, function
1260
# object, and decorate it to be a new D-Bus signal
1261
# with the alternate D-Bus interface name
1262
if sys.version_info.major == 2:
1263
new_function = types.FunctionType(
1264
nonmethod_func.func_code,
1265
nonmethod_func.func_globals,
1266
nonmethod_func.func_name,
1267
nonmethod_func.func_defaults,
1268
nonmethod_func.func_closure)
1270
new_function = types.FunctionType(
1271
nonmethod_func.__code__,
1272
nonmethod_func.__globals__,
1273
nonmethod_func.__name__,
1274
nonmethod_func.__defaults__,
1275
nonmethod_func.__closure__)
1276
new_function = (dbus.service.signal(
1278
attribute._dbus_signature)(new_function))
1279
# Copy annotations, if any
1281
new_function._dbus_annotations = dict(
1282
attribute._dbus_annotations)
1283
except AttributeError:
1285
# Define a creator of a function to call both the
1286
# original and alternate functions, so both the
1287
# original and alternate signals gets sent when
1288
# the function is called
1289
def fixscope(func1, func2):
1290
"""This function is a scope container to pass
1291
func1 and func2 to the "call_both" function
1292
outside of its arguments"""
1294
@functools.wraps(func2)
1295
def call_both(*args, **kwargs):
1296
"""This function will emit two D-Bus
1297
signals by calling func1 and func2"""
1298
func1(*args, **kwargs)
1299
func2(*args, **kwargs)
1300
# Make wrapper function look like a D-Bus signal
1301
for name, attr in inspect.getmembers(func2):
1302
if name.startswith("_dbus_"):
1303
setattr(call_both, name, attr)
1306
# Create the "call_both" function and add it to
1308
attr[attrname] = fixscope(attribute, new_function)
1309
# Is this a D-Bus method?
1310
elif getattr(attribute, "_dbus_is_method", False):
1311
# Create a new, but exactly alike, function
1312
# object. Decorate it to be a new D-Bus method
1313
# with the alternate D-Bus interface name. Add it
1316
dbus.service.method(
1318
attribute._dbus_in_signature,
1319
attribute._dbus_out_signature)
1320
(types.FunctionType(attribute.func_code,
1321
attribute.func_globals,
1322
attribute.func_name,
1323
attribute.func_defaults,
1324
attribute.func_closure)))
1325
# Copy annotations, if any
1327
attr[attrname]._dbus_annotations = dict(
1328
attribute._dbus_annotations)
1329
except AttributeError:
1331
# Is this a D-Bus property?
1332
elif getattr(attribute, "_dbus_is_property", False):
1333
# Create a new, but exactly alike, function
1334
# object, and decorate it to be a new D-Bus
1335
# property with the alternate D-Bus interface
1336
# name. Add it to the class.
1337
attr[attrname] = (dbus_service_property(
1338
alt_interface, attribute._dbus_signature,
1339
attribute._dbus_access,
1340
attribute._dbus_get_args_options
1342
(types.FunctionType(
1343
attribute.func_code,
1344
attribute.func_globals,
1345
attribute.func_name,
1346
attribute.func_defaults,
1347
attribute.func_closure)))
1348
# Copy annotations, if any
1350
attr[attrname]._dbus_annotations = dict(
1351
attribute._dbus_annotations)
1352
except AttributeError:
1354
# Is this a D-Bus interface?
1355
elif getattr(attribute, "_dbus_is_interface", False):
1356
# Create a new, but exactly alike, function
1357
# object. Decorate it to be a new D-Bus interface
1358
# with the alternate D-Bus interface name. Add it
1361
dbus_interface_annotations(alt_interface)
1362
(types.FunctionType(attribute.func_code,
1363
attribute.func_globals,
1364
attribute.func_name,
1365
attribute.func_defaults,
1366
attribute.func_closure)))
1368
# Deprecate all alternate interfaces
1369
iname="_AlternateDBusNames_interface_annotation{}"
1370
for interface_name in interface_names:
1372
@dbus_interface_annotations(interface_name)
1374
return { "org.freedesktop.DBus.Deprecated":
1376
# Find an unused name
1377
for aname in (iname.format(i)
1378
for i in itertools.count()):
1379
if aname not in attr:
1383
# Replace the class with a new subclass of it with
1384
# methods, signals, etc. as created above.
1385
cls = type(b"{}Alternate".format(cls.__name__),
1392
@alternate_dbus_interfaces({"se.recompile.Mandos":
1393
"se.bsnet.fukt.Mandos"})
647
if (if_tag.getAttribute(u"name")
648
== u"org.freedesktop.DBus.Properties"):
649
for cn in if_tag.getElementsByTagName(u"method"):
650
if cn.getAttribute(u"name") == u"Get":
651
for arg in cn.getElementsByTagName(u"arg"):
652
if (arg.getAttribute(u"direction")
654
arg.setAttribute(u"name", u"value")
655
elif cn.getAttribute(u"name") == u"GetAll":
656
for arg in cn.getElementsByTagName(u"arg"):
657
if (arg.getAttribute(u"direction")
659
arg.setAttribute(u"name", u"props")
660
xmlstring = document.toxml(u"utf-8")
662
except (AttributeError, xml.dom.DOMException,
663
xml.parsers.expat.ExpatError), error:
664
logger.error(u"Failed to override Introspection method",
1394
669
class ClientDBus(Client, DBusObjectWithProperties):
1395
670
"""A Client class using D-Bus
1411
680
Client.__init__(self, *args, **kwargs)
1412
681
# Only now, when this client is initialized, can it show up on
1414
client_object_name = str(self.name).translate(
1415
{ord("."): ord("_"),
1416
ord("-"): ord("_")})
1417
self.dbus_object_path = dbus.ObjectPath(
1418
"/clients/" + client_object_name)
683
self.dbus_object_path = (dbus.ObjectPath
685
+ self.name.replace(u".", u"_")))
1419
686
DBusObjectWithProperties.__init__(self, self.bus,
1420
687
self.dbus_object_path)
1422
def notifychangeproperty(transform_func, dbus_name,
1423
type_func=lambda x: x,
1425
invalidate_only=False,
1426
_interface=_interface):
1427
""" Modify a variable so that it's a property which announces
1428
its changes to DBus.
1430
transform_fun: Function that takes a value and a variant_level
1431
and transforms it to a D-Bus type.
1432
dbus_name: D-Bus name of the variable
1433
type_func: Function that transform the value before sending it
1434
to the D-Bus. Default: no transform
1435
variant_level: D-Bus variant level. Default: 1
1437
attrname = "_{}".format(dbus_name)
1439
def setter(self, value):
1440
if hasattr(self, "dbus_object_path"):
1441
if (not hasattr(self, attrname) or
1442
type_func(getattr(self, attrname, None))
1443
!= type_func(value)):
1445
self.PropertiesChanged(
1446
_interface, dbus.Dictionary(),
1447
dbus.Array((dbus_name, )))
1449
dbus_value = transform_func(
1451
variant_level = variant_level)
1452
self.PropertyChanged(dbus.String(dbus_name),
1454
self.PropertiesChanged(
1456
dbus.Dictionary({ dbus.String(dbus_name):
1459
setattr(self, attrname, value)
1461
return property(lambda self: getattr(self, attrname), setter)
1463
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1464
approvals_pending = notifychangeproperty(dbus.Boolean,
1467
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1468
last_enabled = notifychangeproperty(datetime_to_dbus,
1470
checker = notifychangeproperty(
1471
dbus.Boolean, "CheckerRunning",
1472
type_func = lambda checker: checker is not None)
1473
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1475
last_checker_status = notifychangeproperty(dbus.Int16,
1476
"LastCheckerStatus")
1477
last_approval_request = notifychangeproperty(
1478
datetime_to_dbus, "LastApprovalRequest")
1479
approved_by_default = notifychangeproperty(dbus.Boolean,
1480
"ApprovedByDefault")
1481
approval_delay = notifychangeproperty(
1482
dbus.UInt64, "ApprovalDelay",
1483
type_func = lambda td: td.total_seconds() * 1000)
1484
approval_duration = notifychangeproperty(
1485
dbus.UInt64, "ApprovalDuration",
1486
type_func = lambda td: td.total_seconds() * 1000)
1487
host = notifychangeproperty(dbus.String, "Host")
1488
timeout = notifychangeproperty(
1489
dbus.UInt64, "Timeout",
1490
type_func = lambda td: td.total_seconds() * 1000)
1491
extended_timeout = notifychangeproperty(
1492
dbus.UInt64, "ExtendedTimeout",
1493
type_func = lambda td: td.total_seconds() * 1000)
1494
interval = notifychangeproperty(
1495
dbus.UInt64, "Interval",
1496
type_func = lambda td: td.total_seconds() * 1000)
1497
checker_command = notifychangeproperty(dbus.String, "Checker")
1498
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1499
invalidate_only=True)
1501
del notifychangeproperty
690
def _datetime_to_dbus(dt, variant_level=0):
691
"""Convert a UTC datetime.datetime() to a D-Bus type."""
692
return dbus.String(dt.isoformat(),
693
variant_level=variant_level)
696
oldstate = getattr(self, u"enabled", False)
697
r = Client.enable(self)
698
if oldstate != self.enabled:
700
self.PropertyChanged(dbus.String(u"enabled"),
701
dbus.Boolean(True, variant_level=1))
702
self.PropertyChanged(
703
dbus.String(u"last_enabled"),
704
self._datetime_to_dbus(self.last_enabled,
708
def disable(self, quiet = False):
709
oldstate = getattr(self, u"enabled", False)
710
r = Client.disable(self, quiet=quiet)
711
if not quiet and oldstate != self.enabled:
713
self.PropertyChanged(dbus.String(u"enabled"),
714
dbus.Boolean(False, variant_level=1))
1503
717
def __del__(self, *args, **kwargs):
1505
719
self.remove_from_connection()
1506
720
except LookupError:
1508
if hasattr(DBusObjectWithProperties, "__del__"):
722
if hasattr(DBusObjectWithProperties, u"__del__"):
1509
723
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1510
724
Client.__del__(self, *args, **kwargs)
1512
def checker_callback(self, source, condition,
1513
connection, command, *args, **kwargs):
1514
ret = Client.checker_callback(self, source, condition,
1515
connection, command, *args,
1517
exitstatus = self.last_checker_status
726
def checker_callback(self, pid, condition, command,
728
self.checker_callback_tag = None
731
self.PropertyChanged(dbus.String(u"checker_running"),
732
dbus.Boolean(False, variant_level=1))
733
if os.WIFEXITED(condition):
734
exitstatus = os.WEXITSTATUS(condition)
1519
735
# Emit D-Bus signal
1520
736
self.CheckerCompleted(dbus.Int16(exitstatus),
1521
# This is specific to GNU libC
1522
dbus.Int64(exitstatus << 8),
737
dbus.Int64(condition),
1523
738
dbus.String(command))
1525
740
# Emit D-Bus signal
1526
741
self.CheckerCompleted(dbus.Int16(-1),
1528
# This is specific to GNU libC
1530
| self.last_checker_signal),
742
dbus.Int64(condition),
1531
743
dbus.String(command))
745
return Client.checker_callback(self, pid, condition, command,
748
def checked_ok(self, *args, **kwargs):
749
r = Client.checked_ok(self, *args, **kwargs)
751
self.PropertyChanged(
752
dbus.String(u"last_checked_ok"),
753
(self._datetime_to_dbus(self.last_checked_ok,
1534
757
def start_checker(self, *args, **kwargs):
1535
old_checker_pid = getattr(self.checker, "pid", None)
758
old_checker = self.checker
759
if self.checker is not None:
760
old_checker_pid = self.checker.pid
762
old_checker_pid = None
1536
763
r = Client.start_checker(self, *args, **kwargs)
1537
764
# Only if new checker process was started
1538
765
if (self.checker is not None
1539
766
and old_checker_pid != self.checker.pid):
1540
767
# Emit D-Bus signal
1541
768
self.CheckerStarted(self.current_checker_command)
1544
def _reset_approved(self):
1545
self.approved = None
1548
def approve(self, value=True):
1549
self.approved = value
1550
gobject.timeout_add(int(self.approval_duration.total_seconds()
1551
* 1000), self._reset_approved)
1552
self.send_changedstate()
769
self.PropertyChanged(
770
dbus.String(u"checker_running"),
771
dbus.Boolean(True, variant_level=1))
774
def stop_checker(self, *args, **kwargs):
775
old_checker = getattr(self, u"checker", None)
776
r = Client.stop_checker(self, *args, **kwargs)
777
if (old_checker is not None
778
and getattr(self, u"checker", None) is None):
779
self.PropertyChanged(dbus.String(u"checker_running"),
780
dbus.Boolean(False, variant_level=1))
1554
783
## D-Bus methods, signals & properties
784
_interface = u"se.bsnet.fukt.Mandos.Client"
1560
788
# CheckerCompleted - signal
1561
@dbus.service.signal(_interface, signature="nxs")
789
@dbus.service.signal(_interface, signature=u"nxs")
1562
790
def CheckerCompleted(self, exitcode, waitstatus, command):
1566
794
# CheckerStarted - signal
1567
@dbus.service.signal(_interface, signature="s")
795
@dbus.service.signal(_interface, signature=u"s")
1568
796
def CheckerStarted(self, command):
1572
800
# PropertyChanged - signal
1573
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1574
@dbus.service.signal(_interface, signature="sv")
801
@dbus.service.signal(_interface, signature=u"sv")
1575
802
def PropertyChanged(self, property, value):
1874
993
Note: This will run in its own forked process."""
1876
995
def handle(self):
1877
with contextlib.closing(self.server.child_pipe) as child_pipe:
1878
logger.info("TCP connection from: %s",
1879
str(self.client_address))
1880
logger.debug("Pipe FD: %d",
1881
self.server.child_pipe.fileno())
996
logger.info(u"TCP connection from: %s",
997
unicode(self.client_address))
998
logger.debug(u"IPC Pipe FD: %d",
999
self.server.child_pipe[1].fileno())
1000
# Open IPC pipe to parent process
1001
with contextlib.nested(self.server.child_pipe[1],
1002
self.server.parent_pipe[0]
1003
) as (ipc, ipc_return):
1004
session = (gnutls.connection
1005
.ClientSession(self.request,
1007
.X509Credentials()))
1883
session = gnutls.connection.ClientSession(
1884
self.request, gnutls.connection .X509Credentials())
1009
line = self.request.makefile().readline()
1010
logger.debug(u"Protocol version: %r", line)
1012
if int(line.strip().split()[0]) > 1:
1014
except (ValueError, IndexError, RuntimeError), error:
1015
logger.error(u"Unknown protocol version: %s", error)
1886
1018
# Note: gnutls.connection.X509Credentials is really a
1887
1019
# generic GnuTLS certificate credentials object so long as
1888
1020
# no X.509 keys are added to it. Therefore, we can use it
1889
1021
# here despite using OpenPGP certificates.
1891
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1892
# "+AES-256-CBC", "+SHA1",
1893
# "+COMP-NULL", "+CTYPE-OPENPGP",
1023
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1024
# u"+AES-256-CBC", u"+SHA1",
1025
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1895
1027
# Use a fallback default, since this MUST be set.
1896
1028
priority = self.server.gnutls_priority
1897
1029
if priority is None:
1899
gnutls.library.functions.gnutls_priority_set_direct(
1900
session._c_object, priority, None)
1902
# Start communication using the Mandos protocol
1903
# Get protocol number
1904
line = self.request.makefile().readline()
1905
logger.debug("Protocol version: %r", line)
1907
if int(line.strip().split()[0]) > 1:
1908
raise RuntimeError(line)
1909
except (ValueError, IndexError, RuntimeError) as error:
1910
logger.error("Unknown protocol version: %s", error)
1913
# Start GnuTLS connection
1030
priority = u"NORMAL"
1031
(gnutls.library.functions
1032
.gnutls_priority_set_direct(session._c_object,
1915
1036
session.handshake()
1916
except gnutls.errors.GNUTLSError as error:
1917
logger.warning("Handshake failed: %s", error)
1037
except gnutls.errors.GNUTLSError, error:
1038
logger.warning(u"Handshake failed: %s", error)
1918
1039
# Do not run session.bye() here: the session is not
1919
1040
# established. Just abandon the request.
1921
logger.debug("Handshake succeeded")
1923
approval_required = False
1042
logger.debug(u"Handshake succeeded")
1926
fpr = self.fingerprint(
1927
self.peer_certificate(session))
1929
gnutls.errors.GNUTLSError) as error:
1930
logger.warning("Bad certificate: %s", error)
1932
logger.debug("Fingerprint: %s", fpr)
1935
client = ProxyClient(child_pipe, fpr,
1936
self.client_address)
1940
if client.approval_delay:
1941
delay = client.approval_delay
1942
client.approvals_pending += 1
1943
approval_required = True
1946
if not client.enabled:
1947
logger.info("Client %s is disabled",
1949
if self.server.use_dbus:
1951
client.Rejected("Disabled")
1954
if client.approved or not client.approval_delay:
1955
#We are approved or approval is disabled
1045
fpr = self.fingerprint(self.peer_certificate
1047
except (TypeError, gnutls.errors.GNUTLSError), error:
1048
logger.warning(u"Bad certificate: %s", error)
1050
logger.debug(u"Fingerprint: %s", fpr)
1052
for c in self.server.clients:
1053
if c.fingerprint == fpr:
1957
elif client.approved is None:
1958
logger.info("Client %s needs approval",
1960
if self.server.use_dbus:
1962
client.NeedApproval(
1963
client.approval_delay.total_seconds()
1964
* 1000, client.approved_by_default)
1966
logger.warning("Client %s was not approved",
1968
if self.server.use_dbus:
1970
client.Rejected("Denied")
1973
#wait until timeout or approved
1974
time = datetime.datetime.now()
1975
client.changedstate.acquire()
1976
client.changedstate.wait(delay.total_seconds())
1977
client.changedstate.release()
1978
time2 = datetime.datetime.now()
1979
if (time2 - time) >= delay:
1980
if not client.approved_by_default:
1981
logger.warning("Client %s timed out while"
1982
" waiting for approval",
1984
if self.server.use_dbus:
1986
client.Rejected("Approval timed out")
1991
delay -= time2 - time
1057
ipc.write(u"NOTFOUND %s %s\n"
1058
% (fpr, unicode(self.client_address)))
1060
# Have to check if client.enabled, since it is
1061
# possible that the client was disabled since the
1062
# GnuTLS session was established.
1063
ipc.write(u"GETATTR enabled %s\n" % fpr)
1064
enabled = pickle.load(ipc_return)
1066
ipc.write(u"DISABLED %s\n" % client.name)
1068
ipc.write(u"SENDING %s\n" % client.name)
1994
1070
while sent_size < len(client.secret):
1996
sent = session.send(client.secret[sent_size:])
1997
except gnutls.errors.GNUTLSError as error:
1998
logger.warning("gnutls send failed",
2001
logger.debug("Sent: %d, remaining: %d", sent,
2002
len(client.secret) - (sent_size
1071
sent = session.send(client.secret[sent_size:])
1072
logger.debug(u"Sent: %d, remaining: %d",
1073
sent, len(client.secret)
1074
- (sent_size + sent))
2004
1075
sent_size += sent
2006
logger.info("Sending secret to %s", client.name)
2007
# bump the timeout using extended_timeout
2008
client.bump_timeout(client.extended_timeout)
2009
if self.server.use_dbus:
2014
if approval_required:
2015
client.approvals_pending -= 1
2018
except gnutls.errors.GNUTLSError as error:
2019
logger.warning("GnuTLS bye failed",
2023
1080
def peer_certificate(session):
2024
1081
"Return the peer's OpenPGP certificate as a bytestring"
2025
1082
# If not an OpenPGP certificate...
2026
if (gnutls.library.functions.gnutls_certificate_type_get(
1083
if (gnutls.library.functions
1084
.gnutls_certificate_type_get(session._c_object)
2028
1085
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
2029
1086
# ...do the normal thing
2030
1087
return session.peer_certificate
2224
1237
Assumes a gobject.MainLoop event loop.
2227
1239
def __init__(self, server_address, RequestHandlerClass,
2231
gnutls_priority=None,
1240
interface=None, use_ipv6=True, clients=None,
1241
gnutls_priority=None, use_dbus=True):
2234
1242
self.enabled = False
2235
1243
self.clients = clients
2236
1244
if self.clients is None:
1245
self.clients = set()
2238
1246
self.use_dbus = use_dbus
2239
1247
self.gnutls_priority = gnutls_priority
2240
1248
IPv6_TCPServer.__init__(self, server_address,
2241
1249
RequestHandlerClass,
2242
1250
interface = interface,
2243
use_ipv6 = use_ipv6,
2244
socketfd = socketfd)
1251
use_ipv6 = use_ipv6)
2246
1252
def server_activate(self):
2247
1253
if self.enabled:
2248
1254
return socketserver.TCPServer.server_activate(self)
2250
1255
def enable(self):
2251
1256
self.enabled = True
2253
def add_pipe(self, parent_pipe, proc):
1257
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
2254
1258
# Call "handle_ipc" for both data and EOF events
2255
gobject.io_add_watch(
2256
parent_pipe.fileno(),
2257
gobject.IO_IN | gobject.IO_HUP,
2258
functools.partial(self.handle_ipc,
2259
parent_pipe = parent_pipe,
2262
def handle_ipc(self, source, condition,
2265
client_object=None):
2266
# error, or the other end of multiprocessing.Pipe has closed
2267
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2268
# Wait for other process to exit
2272
# Read a request from the child
2273
request = parent_pipe.recv()
2274
command = request[0]
2276
if command == 'init':
2278
address = request[2]
2280
for c in self.clients.itervalues():
2281
if c.fingerprint == fpr:
2285
logger.info("Client not found for fingerprint: %s, ad"
2286
"dress: %s", fpr, address)
2289
mandos_dbus_service.ClientNotFound(fpr,
2291
parent_pipe.send(False)
2294
gobject.io_add_watch(
2295
parent_pipe.fileno(),
2296
gobject.IO_IN | gobject.IO_HUP,
2297
functools.partial(self.handle_ipc,
2298
parent_pipe = parent_pipe,
2300
client_object = client))
2301
parent_pipe.send(True)
2302
# remove the old hook in favor of the new above hook on
2305
if command == 'funcall':
2306
funcname = request[1]
2310
parent_pipe.send(('data', getattr(client_object,
2314
if command == 'getattr':
2315
attrname = request[1]
2316
if isinstance(client_object.__getattribute__(attrname),
2317
collections.Callable):
2318
parent_pipe.send(('function', ))
2321
'data', client_object.__getattribute__(attrname)))
2323
if command == 'setattr':
2324
attrname = request[1]
2326
setattr(client_object, attrname, value)
1259
gobject.io_add_watch(child_pipe_fd.fileno(),
1260
gobject.IO_IN | gobject.IO_HUP,
1261
functools.partial(self.handle_ipc,
1262
reply = parent_pipe_fd,
1263
sender= child_pipe_fd))
1264
def handle_ipc(self, source, condition, reply=None, sender=None):
1266
gobject.IO_IN: u"IN", # There is data to read.
1267
gobject.IO_OUT: u"OUT", # Data can be written (without
1269
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1270
gobject.IO_ERR: u"ERR", # Error condition.
1271
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1272
# broken, usually for pipes and
1275
conditions_string = ' | '.join(name
1277
condition_names.iteritems()
1278
if cond & condition)
1279
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1282
# Read a line from the file object
1283
cmdline = sender.readline()
1284
if not cmdline: # Empty line means end of file
1285
# close the IPC pipes
1289
# Stop calling this function
1292
logger.debug(u"IPC command: %r", cmdline)
1294
# Parse and act on command
1295
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1297
if cmd == u"NOTFOUND":
1298
fpr, address = args.split(None, 1)
1299
logger.warning(u"Client not found for fingerprint: %s, ad"
1300
u"dress: %s", fpr, address)
1303
mandos_dbus_service.ClientNotFound(fpr, address)
1304
elif cmd == u"DISABLED":
1305
for client in self.clients:
1306
if client.name == args:
1307
logger.warning(u"Client %s is disabled", args)
1313
logger.error(u"Unknown client %s is disabled", args)
1314
elif cmd == u"SENDING":
1315
for client in self.clients:
1316
if client.name == args:
1317
logger.info(u"Sending secret to %s", client.name)
1324
logger.error(u"Sending secret to unknown client %s",
1326
elif cmd == u"GETATTR":
1327
attr_name, fpr = args.split(None, 1)
1328
for client in self.clients:
1329
if client.fingerprint == fpr:
1330
attr_value = getattr(client, attr_name, None)
1331
logger.debug("IPC reply: %r", attr_value)
1332
pickle.dump(attr_value, reply)
1335
logger.error(u"Client %s on address %s requesting "
1336
u"attribute %s not found", fpr, address,
1338
pickle.dump(None, reply)
1340
logger.error(u"Unknown IPC command: %r", cmdline)
1342
# Keep calling this function
2331
def rfc3339_duration_to_delta(duration):
2332
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2334
>>> rfc3339_duration_to_delta("P7D")
2335
datetime.timedelta(7)
2336
>>> rfc3339_duration_to_delta("PT60S")
2337
datetime.timedelta(0, 60)
2338
>>> rfc3339_duration_to_delta("PT60M")
2339
datetime.timedelta(0, 3600)
2340
>>> rfc3339_duration_to_delta("PT24H")
2341
datetime.timedelta(1)
2342
>>> rfc3339_duration_to_delta("P1W")
2343
datetime.timedelta(7)
2344
>>> rfc3339_duration_to_delta("PT5M30S")
2345
datetime.timedelta(0, 330)
2346
>>> rfc3339_duration_to_delta("P1DT3M20S")
2347
datetime.timedelta(1, 200)
2350
# Parsing an RFC 3339 duration with regular expressions is not
2351
# possible - there would have to be multiple places for the same
2352
# values, like seconds. The current code, while more esoteric, is
2353
# cleaner without depending on a parsing library. If Python had a
2354
# built-in library for parsing we would use it, but we'd like to
2355
# avoid excessive use of external libraries.
2357
# New type for defining tokens, syntax, and semantics all-in-one
2358
Token = collections.namedtuple("Token", (
2359
"regexp", # To match token; if "value" is not None, must have
2360
# a "group" containing digits
2361
"value", # datetime.timedelta or None
2362
"followers")) # Tokens valid after this token
2363
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2364
# the "duration" ABNF definition in RFC 3339, Appendix A.
2365
token_end = Token(re.compile(r"$"), None, frozenset())
2366
token_second = Token(re.compile(r"(\d+)S"),
2367
datetime.timedelta(seconds=1),
2368
frozenset((token_end, )))
2369
token_minute = Token(re.compile(r"(\d+)M"),
2370
datetime.timedelta(minutes=1),
2371
frozenset((token_second, token_end)))
2372
token_hour = Token(re.compile(r"(\d+)H"),
2373
datetime.timedelta(hours=1),
2374
frozenset((token_minute, token_end)))
2375
token_time = Token(re.compile(r"T"),
2377
frozenset((token_hour, token_minute,
2379
token_day = Token(re.compile(r"(\d+)D"),
2380
datetime.timedelta(days=1),
2381
frozenset((token_time, token_end)))
2382
token_month = Token(re.compile(r"(\d+)M"),
2383
datetime.timedelta(weeks=4),
2384
frozenset((token_day, token_end)))
2385
token_year = Token(re.compile(r"(\d+)Y"),
2386
datetime.timedelta(weeks=52),
2387
frozenset((token_month, token_end)))
2388
token_week = Token(re.compile(r"(\d+)W"),
2389
datetime.timedelta(weeks=1),
2390
frozenset((token_end, )))
2391
token_duration = Token(re.compile(r"P"), None,
2392
frozenset((token_year, token_month,
2393
token_day, token_time,
2395
# Define starting values
2396
value = datetime.timedelta() # Value so far
2398
followers = frozenset((token_duration, )) # Following valid tokens
2399
s = duration # String left to parse
2400
# Loop until end token is found
2401
while found_token is not token_end:
2402
# Search for any currently valid tokens
2403
for token in followers:
2404
match = token.regexp.match(s)
2405
if match is not None:
2407
if token.value is not None:
2408
# Value found, parse digits
2409
factor = int(match.group(1), 10)
2410
# Add to value so far
2411
value += factor * token.value
2412
# Strip token from string
2413
s = token.regexp.sub("", s, 1)
2416
# Set valid next tokens
2417
followers = found_token.followers
2420
# No currently valid tokens were found
2421
raise ValueError("Invalid RFC 3339 duration: {!r}"
2427
1346
def string_to_delta(interval):
2428
1347
"""Parse a string and return a datetime.timedelta
2430
>>> string_to_delta('7d')
1349
>>> string_to_delta(u'7d')
2431
1350
datetime.timedelta(7)
2432
>>> string_to_delta('60s')
1351
>>> string_to_delta(u'60s')
2433
1352
datetime.timedelta(0, 60)
2434
>>> string_to_delta('60m')
1353
>>> string_to_delta(u'60m')
2435
1354
datetime.timedelta(0, 3600)
2436
>>> string_to_delta('24h')
1355
>>> string_to_delta(u'24h')
2437
1356
datetime.timedelta(1)
2438
>>> string_to_delta('1w')
1357
>>> string_to_delta(u'1w')
2439
1358
datetime.timedelta(7)
2440
>>> string_to_delta('5m 30s')
1359
>>> string_to_delta(u'5m 30s')
2441
1360
datetime.timedelta(0, 330)
2445
return rfc3339_duration_to_delta(interval)
2449
1362
timevalue = datetime.timedelta(0)
2450
1363
for s in interval.split():
1365
suffix = unicode(s[-1])
2453
1366
value = int(s[:-1])
2455
1368
delta = datetime.timedelta(value)
1369
elif suffix == u"s":
2457
1370
delta = datetime.timedelta(0, value)
1371
elif suffix == u"m":
2459
1372
delta = datetime.timedelta(0, 0, 0, 0, value)
1373
elif suffix == u"h":
2461
1374
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1375
elif suffix == u"w":
2463
1376
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2465
raise ValueError("Unknown suffix {!r}".format(suffix))
2466
except IndexError as e:
2467
raise ValueError(*(e.args))
1378
raise ValueError(u"Unknown suffix %r" % suffix)
1379
except (ValueError, IndexError), e:
1380
raise ValueError(e.message)
2468
1381
timevalue += delta
2469
1382
return timevalue
1385
def if_nametoindex(interface):
1386
"""Call the C function if_nametoindex(), or equivalent
1388
Note: This function cannot accept a unicode string."""
1389
global if_nametoindex
1391
if_nametoindex = (ctypes.cdll.LoadLibrary
1392
(ctypes.util.find_library(u"c"))
1394
except (OSError, AttributeError):
1395
logger.warning(u"Doing if_nametoindex the hard way")
1396
def if_nametoindex(interface):
1397
"Get an interface index the hard way, i.e. using fcntl()"
1398
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1399
with contextlib.closing(socket.socket()) as s:
1400
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1401
struct.pack(str(u"16s16x"),
1403
interface_index = struct.unpack(str(u"I"),
1405
return interface_index
1406
return if_nametoindex(interface)
2472
1409
def daemon(nochdir = False, noclose = False):
2473
1410
"""See daemon(3). Standard BSD Unix function.
2499
1436
##################################################################
2500
1437
# Parsing of options, both command line and config file
2502
parser = argparse.ArgumentParser()
2503
parser.add_argument("-v", "--version", action="version",
2504
version = "%(prog)s {}".format(version),
2505
help="show version number and exit")
2506
parser.add_argument("-i", "--interface", metavar="IF",
2507
help="Bind to interface IF")
2508
parser.add_argument("-a", "--address",
2509
help="Address to listen for requests on")
2510
parser.add_argument("-p", "--port", type=int,
2511
help="Port number to receive requests on")
2512
parser.add_argument("--check", action="store_true",
2513
help="Run self-test")
2514
parser.add_argument("--debug", action="store_true",
2515
help="Debug mode; run in foreground and log"
2516
" to terminal", default=None)
2517
parser.add_argument("--debuglevel", metavar="LEVEL",
2518
help="Debug level for stdout output")
2519
parser.add_argument("--priority", help="GnuTLS"
2520
" priority string (see GnuTLS documentation)")
2521
parser.add_argument("--servicename",
2522
metavar="NAME", help="Zeroconf service name")
2523
parser.add_argument("--configdir",
2524
default="/etc/mandos", metavar="DIR",
2525
help="Directory to search for configuration"
2527
parser.add_argument("--no-dbus", action="store_false",
2528
dest="use_dbus", help="Do not provide D-Bus"
2529
" system bus interface", default=None)
2530
parser.add_argument("--no-ipv6", action="store_false",
2531
dest="use_ipv6", help="Do not use IPv6",
2533
parser.add_argument("--no-restore", action="store_false",
2534
dest="restore", help="Do not restore stored"
2535
" state", default=None)
2536
parser.add_argument("--socket", type=int,
2537
help="Specify a file descriptor to a network"
2538
" socket to use instead of creating one")
2539
parser.add_argument("--statedir", metavar="DIR",
2540
help="Directory to save/restore state in")
2541
parser.add_argument("--foreground", action="store_true",
2542
help="Run in foreground", default=None)
2543
parser.add_argument("--no-zeroconf", action="store_false",
2544
dest="zeroconf", help="Do not use Zeroconf",
2547
options = parser.parse_args()
1439
parser = optparse.OptionParser(version = "%%prog %s" % version)
1440
parser.add_option("-i", u"--interface", type=u"string",
1441
metavar="IF", help=u"Bind to interface IF")
1442
parser.add_option("-a", u"--address", type=u"string",
1443
help=u"Address to listen for requests on")
1444
parser.add_option("-p", u"--port", type=u"int",
1445
help=u"Port number to receive requests on")
1446
parser.add_option("--check", action=u"store_true",
1447
help=u"Run self-test")
1448
parser.add_option("--debug", action=u"store_true",
1449
help=u"Debug mode; run in foreground and log to"
1451
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1452
u" priority string (see GnuTLS documentation)")
1453
parser.add_option("--servicename", type=u"string",
1454
metavar=u"NAME", help=u"Zeroconf service name")
1455
parser.add_option("--configdir", type=u"string",
1456
default=u"/etc/mandos", metavar=u"DIR",
1457
help=u"Directory to search for configuration"
1459
parser.add_option("--no-dbus", action=u"store_false",
1460
dest=u"use_dbus", help=u"Do not provide D-Bus"
1461
u" system bus interface")
1462
parser.add_option("--no-ipv6", action=u"store_false",
1463
dest=u"use_ipv6", help=u"Do not use IPv6")
1464
options = parser.parse_args()[0]
2549
1466
if options.check:
2551
fail_count, test_count = doctest.testmod()
2552
sys.exit(os.EX_OK if fail_count == 0 else 1)
2554
1471
# Default values for config file for server-global settings
2555
server_defaults = { "interface": "",
2560
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2561
":+SIGN-DSA-SHA256",
2562
"servicename": "Mandos",
2568
"statedir": "/var/lib/mandos",
2569
"foreground": "False",
1472
server_defaults = { u"interface": u"",
1477
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1478
u"servicename": u"Mandos",
1479
u"use_dbus": u"True",
1480
u"use_ipv6": u"True",
2573
1483
# Parse config file for server-global settings
2574
1484
server_config = configparser.SafeConfigParser(server_defaults)
2575
1485
del server_defaults
2576
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1486
server_config.read(os.path.join(options.configdir,
2577
1488
# Convert the SafeConfigParser object to a dict
2578
1489
server_settings = server_config.defaults()
2579
1490
# Use the appropriate methods on the non-string config options
2580
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2581
server_settings[option] = server_config.getboolean("DEFAULT",
1491
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1492
server_settings[option] = server_config.getboolean(u"DEFAULT",
2583
1494
if server_settings["port"]:
2584
server_settings["port"] = server_config.getint("DEFAULT",
2586
if server_settings["socket"]:
2587
server_settings["socket"] = server_config.getint("DEFAULT",
2589
# Later, stdin will, and stdout and stderr might, be dup'ed
2590
# over with an opened os.devnull. But we don't want this to
2591
# happen with a supplied network socket.
2592
if 0 <= server_settings["socket"] <= 2:
2593
server_settings["socket"] = os.dup(server_settings
1495
server_settings["port"] = server_config.getint(u"DEFAULT",
2595
1497
del server_config
2597
1499
# Override the settings from the config file with command line
2598
1500
# options, if set.
2599
for option in ("interface", "address", "port", "debug",
2600
"priority", "servicename", "configdir", "use_dbus",
2601
"use_ipv6", "debuglevel", "restore", "statedir",
2602
"socket", "foreground", "zeroconf"):
1501
for option in (u"interface", u"address", u"port", u"debug",
1502
u"priority", u"servicename", u"configdir",
1503
u"use_dbus", u"use_ipv6"):
2603
1504
value = getattr(options, option)
2604
1505
if value is not None:
2605
1506
server_settings[option] = value
2607
1508
# Force all strings to be unicode
2608
1509
for option in server_settings.keys():
2609
if isinstance(server_settings[option], bytes):
2610
server_settings[option] = (server_settings[option]
2612
# Force all boolean options to be boolean
2613
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2614
"foreground", "zeroconf"):
2615
server_settings[option] = bool(server_settings[option])
2616
# Debug implies foreground
2617
if server_settings["debug"]:
2618
server_settings["foreground"] = True
1510
if type(server_settings[option]) is str:
1511
server_settings[option] = unicode(server_settings[option])
2619
1512
# Now we have our good server settings in "server_settings"
2621
1514
##################################################################
2623
if (not server_settings["zeroconf"]
2624
and not (server_settings["port"]
2625
or server_settings["socket"] != "")):
2626
parser.error("Needs port or socket to work without Zeroconf")
2628
1516
# For convenience
2629
debug = server_settings["debug"]
2630
debuglevel = server_settings["debuglevel"]
2631
use_dbus = server_settings["use_dbus"]
2632
use_ipv6 = server_settings["use_ipv6"]
2633
stored_state_path = os.path.join(server_settings["statedir"],
2635
foreground = server_settings["foreground"]
2636
zeroconf = server_settings["zeroconf"]
2639
initlogger(debug, logging.DEBUG)
2644
level = getattr(logging, debuglevel.upper())
2645
initlogger(debug, level)
2647
if server_settings["servicename"] != "Mandos":
2648
syslogger.setFormatter(
2649
logging.Formatter('Mandos ({}) [%(process)d]:'
2650
' %(levelname)s: %(message)s'.format(
2651
server_settings["servicename"])))
1517
debug = server_settings[u"debug"]
1518
use_dbus = server_settings[u"use_dbus"]
1519
use_ipv6 = server_settings[u"use_ipv6"]
1522
syslogger.setLevel(logging.WARNING)
1523
console.setLevel(logging.WARNING)
1525
if server_settings[u"servicename"] != u"Mandos":
1526
syslogger.setFormatter(logging.Formatter
1527
(u'Mandos (%s) [%%(process)d]:'
1528
u' %%(levelname)s: %%(message)s'
1529
% server_settings[u"servicename"]))
2653
1531
# Parse config file with clients
2654
client_config = configparser.SafeConfigParser(Client
2656
client_config.read(os.path.join(server_settings["configdir"],
1532
client_defaults = { u"timeout": u"1h",
1534
u"checker": u"fping -q -- %%(host)s",
1537
client_config = configparser.SafeConfigParser(client_defaults)
1538
client_config.read(os.path.join(server_settings[u"configdir"],
2659
1541
global mandos_dbus_service
2660
1542
mandos_dbus_service = None
2663
if server_settings["socket"] != "":
2664
socketfd = server_settings["socket"]
2665
tcp_server = MandosServer(
2666
(server_settings["address"], server_settings["port"]),
2668
interface=(server_settings["interface"] or None),
2670
gnutls_priority=server_settings["priority"],
2674
pidfilename = "/run/mandos.pid"
2675
if not os.path.isdir("/run/."):
2676
pidfilename = "/var/run/mandos.pid"
2679
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2680
except IOError as e:
2681
logger.error("Could not open file %r", pidfilename,
1544
tcp_server = MandosServer((server_settings[u"address"],
1545
server_settings[u"port"]),
1547
interface=server_settings[u"interface"],
1550
server_settings[u"priority"],
1552
pidfilename = u"/var/run/mandos.pid"
1554
pidfile = open(pidfilename, u"w")
1556
logger.error(u"Could not open file %r", pidfilename)
2684
for name in ("_mandos", "mandos", "nobody"):
1559
uid = pwd.getpwnam(u"_mandos").pw_uid
1560
gid = pwd.getpwnam(u"_mandos").pw_gid
2686
uid = pwd.getpwnam(name).pw_uid
2687
gid = pwd.getpwnam(name).pw_gid
1563
uid = pwd.getpwnam(u"mandos").pw_uid
1564
gid = pwd.getpwnam(u"mandos").pw_gid
2689
1565
except KeyError:
1567
uid = pwd.getpwnam(u"nobody").pw_uid
1568
gid = pwd.getpwnam(u"nobody").pw_gid
2697
except OSError as error:
2698
if error.errno != errno.EPERM:
1575
except OSError, error:
1576
if error[0] != errno.EPERM:
1579
# Enable all possible GnuTLS debugging
2702
# Enable all possible GnuTLS debugging
2704
1581
# "Use a log level over 10 to enable all debugging options."
2705
1582
# - GnuTLS manual
2706
1583
gnutls.library.functions.gnutls_global_set_log_level(11)
2708
1585
@gnutls.library.types.gnutls_log_func
2709
1586
def debug_gnutls(level, string):
2710
logger.debug("GnuTLS: %s", string[:-1])
2712
gnutls.library.functions.gnutls_global_set_log_function(
2715
# Redirect stdin so all checkers get /dev/null
2716
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2717
os.dup2(null, sys.stdin.fileno())
2721
# Need to fork before connecting to D-Bus
2723
# Close all input and output, do double fork, etc.
2726
# multiprocessing will use threads, so before we use gobject we
2727
# need to inform gobject that threads will be used.
2728
gobject.threads_init()
1587
logger.debug(u"GnuTLS: %s", string[:-1])
1589
(gnutls.library.functions
1590
.gnutls_global_set_log_function(debug_gnutls))
2730
1592
global main_loop
2731
1593
# From the Avahi example code
2732
DBusGMainLoop(set_as_default=True)
1594
DBusGMainLoop(set_as_default=True )
2733
1595
main_loop = gobject.MainLoop()
2734
1596
bus = dbus.SystemBus()
2735
1597
# End of Avahi example code
2738
bus_name = dbus.service.BusName("se.recompile.Mandos",
2741
old_bus_name = dbus.service.BusName(
2742
"se.bsnet.fukt.Mandos", bus,
2744
except dbus.exceptions.DBusException as e:
2745
logger.error("Disabling D-Bus:", exc_info=e)
1600
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1601
bus, do_not_queue=True)
1602
except dbus.exceptions.NameExistsException, e:
1603
logger.error(unicode(e) + u", disabling D-Bus")
2746
1604
use_dbus = False
2747
server_settings["use_dbus"] = False
1605
server_settings[u"use_dbus"] = False
2748
1606
tcp_server.use_dbus = False
2750
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2751
service = AvahiServiceToSyslog(
2752
name = server_settings["servicename"],
2753
servicetype = "_mandos._tcp",
2754
protocol = protocol,
2756
if server_settings["interface"]:
2757
service.interface = if_nametoindex(
2758
server_settings["interface"].encode("utf-8"))
2760
global multiprocessing_manager
2761
multiprocessing_manager = multiprocessing.Manager()
1607
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1608
service = AvahiService(name = server_settings[u"servicename"],
1609
servicetype = u"_mandos._tcp",
1610
protocol = protocol, bus = bus)
1611
if server_settings["interface"]:
1612
service.interface = (if_nametoindex
1613
(str(server_settings[u"interface"])))
2763
1615
client_class = Client
2765
1617
client_class = functools.partial(ClientDBus, bus = bus)
2767
client_settings = Client.config_parser(client_config)
2768
old_client_settings = {}
2771
# This is used to redirect stdout and stderr for checker processes
2773
wnull = open(os.devnull, "w") # A writable /dev/null
2774
# Only used if server is running in foreground but not in debug
2776
if debug or not foreground:
2779
# Get client data and settings from last running state.
2780
if server_settings["restore"]:
2782
with open(stored_state_path, "rb") as stored_state:
2783
clients_data, old_client_settings = pickle.load(
2785
os.remove(stored_state_path)
2786
except IOError as e:
2787
if e.errno == errno.ENOENT:
2788
logger.warning("Could not load persistent state:"
2789
" {}".format(os.strerror(e.errno)))
2791
logger.critical("Could not load persistent state:",
2794
except EOFError as e:
2795
logger.warning("Could not load persistent state: "
2799
with PGPEngine() as pgp:
2800
for client_name, client in clients_data.items():
2801
# Skip removed clients
2802
if client_name not in client_settings:
2805
# Decide which value to use after restoring saved state.
2806
# We have three different values: Old config file,
2807
# new config file, and saved state.
2808
# New config value takes precedence if it differs from old
2809
# config value, otherwise use saved state.
2810
for name, value in client_settings[client_name].items():
2812
# For each value in new config, check if it
2813
# differs from the old config value (Except for
2814
# the "secret" attribute)
2815
if (name != "secret"
2817
old_client_settings[client_name][name])):
2818
client[name] = value
2822
# Clients who has passed its expire date can still be
2823
# enabled if its last checker was successful. A Client
2824
# whose checker succeeded before we stored its state is
2825
# assumed to have successfully run all checkers during
2827
if client["enabled"]:
2828
if datetime.datetime.utcnow() >= client["expires"]:
2829
if not client["last_checked_ok"]:
2831
"disabling client {} - Client never "
2832
"performed a successful checker".format(
2834
client["enabled"] = False
2835
elif client["last_checker_status"] != 0:
2837
"disabling client {} - Client last"
2838
" checker failed with error code"
2841
client["last_checker_status"]))
2842
client["enabled"] = False
2844
client["expires"] = (
2845
datetime.datetime.utcnow()
2846
+ client["timeout"])
2847
logger.debug("Last checker succeeded,"
2848
" keeping {} enabled".format(
2851
client["secret"] = pgp.decrypt(
2852
client["encrypted_secret"],
2853
client_settings[client_name]["secret"])
2855
# If decryption fails, we use secret from new settings
2856
logger.debug("Failed to decrypt {} old secret".format(
2858
client["secret"] = (client_settings[client_name]
2861
# Add/remove clients based on new changes made to config
2862
for client_name in (set(old_client_settings)
2863
- set(client_settings)):
2864
del clients_data[client_name]
2865
for client_name in (set(client_settings)
2866
- set(old_client_settings)):
2867
clients_data[client_name] = client_settings[client_name]
2869
# Create all client objects
2870
for client_name, client in clients_data.items():
2871
tcp_server.clients[client_name] = client_class(
2874
server_settings = server_settings)
1618
tcp_server.clients.update(set(
1619
client_class(name = section,
1620
config= dict(client_config.items(section)))
1621
for section in client_config.sections()))
2876
1622
if not tcp_server.clients:
2877
logger.warning("No clients defined")
2880
if pidfile is not None:
1623
logger.warning(u"No clients defined")
1626
# Redirect stdin so all checkers get /dev/null
1627
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1628
os.dup2(null, sys.stdin.fileno())
1632
# No console logging
1633
logger.removeHandler(console)
1634
# Close all input and output, do double fork, etc.
2881
1639
pid = os.getpid()
2884
print(pid, file=pidfile)
2886
logger.error("Could not write to file %r with PID %d",
1640
pidfile.write(str(pid) + "\n")
1643
logger.error(u"Could not write to file %r with PID %d",
1646
# "pidfile" was never created
1651
signal.signal(signal.SIGINT, signal.SIG_IGN)
2891
1652
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2892
1653
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2896
@alternate_dbus_interfaces(
2897
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2898
class MandosDBusService(DBusObjectWithObjectManager):
1656
class MandosDBusService(dbus.service.Object):
2899
1657
"""A D-Bus proxy object"""
2901
1658
def __init__(self):
2902
dbus.service.Object.__init__(self, bus, "/")
2904
_interface = "se.recompile.Mandos"
2906
@dbus.service.signal(_interface, signature="o")
1659
dbus.service.Object.__init__(self, bus, u"/")
1660
_interface = u"se.bsnet.fukt.Mandos"
1662
@dbus.service.signal(_interface, signature=u"o")
2907
1663
def ClientAdded(self, objpath):
2911
@dbus.service.signal(_interface, signature="ss")
1667
@dbus.service.signal(_interface, signature=u"ss")
2912
1668
def ClientNotFound(self, fingerprint, address):
2916
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2918
@dbus.service.signal(_interface, signature="os")
1672
@dbus.service.signal(_interface, signature=u"os")
2919
1673
def ClientRemoved(self, objpath, name):
2923
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2925
@dbus.service.method(_interface, out_signature="ao")
1677
@dbus.service.method(_interface, out_signature=u"ao")
2926
1678
def GetAllClients(self):
2928
return dbus.Array(c.dbus_object_path for c in
2929
tcp_server.clients.itervalues())
1680
return dbus.Array(c.dbus_object_path
1681
for c in tcp_server.clients)
2931
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2933
1683
@dbus.service.method(_interface,
2934
out_signature="a{oa{sv}}")
1684
out_signature=u"a{oa{sv}}")
2935
1685
def GetAllClientsWithProperties(self):
2937
1687
return dbus.Dictionary(
2938
{ c.dbus_object_path: c.GetAll(
2939
"se.recompile.Mandos.Client")
2940
for c in tcp_server.clients.itervalues() },
1688
((c.dbus_object_path, c.GetAll(u""))
1689
for c in tcp_server.clients),
1690
signature=u"oa{sv}")
2943
@dbus.service.method(_interface, in_signature="o")
1692
@dbus.service.method(_interface, in_signature=u"o")
2944
1693
def RemoveClient(self, object_path):
2946
for c in tcp_server.clients.itervalues():
1695
for c in tcp_server.clients:
2947
1696
if c.dbus_object_path == object_path:
2948
del tcp_server.clients[c.name]
1697
tcp_server.clients.remove(c)
2949
1698
c.remove_from_connection()
2950
# Don't signal the disabling
1699
# Don't signal anything except ClientRemoved
2951
1700
c.disable(quiet=True)
2952
# Emit D-Bus signal for removal
2953
self.client_removed_signal(c)
1702
self.ClientRemoved(object_path, c.name)
2955
1704
raise KeyError(object_path)
2959
@dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
2960
out_signature = "a{oa{sa{sv}}}")
2961
def GetManagedObjects(self):
2963
return dbus.Dictionary(
2964
{ client.dbus_object_path:
2966
{ interface: client.GetAll(interface)
2968
client._get_all_interface_names()})
2969
for client in tcp_server.clients.values()})
2971
def client_added_signal(self, client):
2972
"""Send the new standard signal and the old signal"""
2974
# New standard signal
2975
self.InterfacesAdded(
2976
client.dbus_object_path,
2978
{ interface: client.GetAll(interface)
2980
client._get_all_interface_names()}))
2982
self.ClientAdded(client.dbus_object_path)
2984
def client_removed_signal(self, client):
2985
"""Send the new standard signal and the old signal"""
2987
# New standard signal
2988
self.InterfacesRemoved(
2989
client.dbus_object_path,
2990
client._get_all_interface_names())
2992
self.ClientRemoved(client.dbus_object_path,
2995
1708
mandos_dbus_service = MandosDBusService()
2998
1711
"Cleanup function; run on exit"
3002
multiprocessing.active_children()
3004
if not (tcp_server.clients or client_settings):
3007
# Store client before exiting. Secrets are encrypted with key
3008
# based on what config file has. If config file is
3009
# removed/edited, old secret will thus be unrecovable.
3011
with PGPEngine() as pgp:
3012
for client in tcp_server.clients.itervalues():
3013
key = client_settings[client.name]["secret"]
3014
client.encrypted_secret = pgp.encrypt(client.secret,
3018
# A list of attributes that can not be pickled
3020
exclude = { "bus", "changedstate", "secret",
3021
"checker", "server_settings" }
3022
for name, typ in inspect.getmembers(dbus.service
3026
client_dict["encrypted_secret"] = (client
3028
for attr in client.client_structure:
3029
if attr not in exclude:
3030
client_dict[attr] = getattr(client, attr)
3032
clients[client.name] = client_dict
3033
del client_settings[client.name]["secret"]
3036
with tempfile.NamedTemporaryFile(
3040
dir=os.path.dirname(stored_state_path),
3041
delete=False) as stored_state:
3042
pickle.dump((clients, client_settings), stored_state)
3043
tempname = stored_state.name
3044
os.rename(tempname, stored_state_path)
3045
except (IOError, OSError) as e:
3051
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
3052
logger.warning("Could not save persistent state: {}"
3053
.format(os.strerror(e.errno)))
3055
logger.warning("Could not save persistent state:",
3059
# Delete all clients, and settings from config
3060
1714
while tcp_server.clients:
3061
name, client = tcp_server.clients.popitem()
1715
client = tcp_server.clients.pop()
3063
1717
client.remove_from_connection()
3064
# Don't signal the disabling
1718
client.disable_hook = None
1719
# Don't signal anything except ClientRemoved
3065
1720
client.disable(quiet=True)
3066
# Emit D-Bus signal for removal
3067
mandos_dbus_service.client_removed_signal(client)
3068
client_settings.clear()
1723
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
3070
1726
atexit.register(cleanup)
3072
for client in tcp_server.clients.itervalues():
1728
for client in tcp_server.clients:
3074
# Emit D-Bus signal for adding
3075
mandos_dbus_service.client_added_signal(client)
3076
# Need to initiate checking of clients
3078
client.init_checker()
1731
mandos_dbus_service.ClientAdded(client.dbus_object_path)
3080
1734
tcp_server.enable()
3081
1735
tcp_server.server_activate()
3083
1737
# Find out what port we got
3085
service.port = tcp_server.socket.getsockname()[1]
1738
service.port = tcp_server.socket.getsockname()[1]
3087
logger.info("Now listening on address %r, port %d,"
3088
" flowinfo %d, scope_id %d",
3089
*tcp_server.socket.getsockname())
1740
logger.info(u"Now listening on address %r, port %d,"
1741
" flowinfo %d, scope_id %d"
1742
% tcp_server.socket.getsockname())
3091
logger.info("Now listening on address %r, port %d",
3092
*tcp_server.socket.getsockname())
1744
logger.info(u"Now listening on address %r, port %d"
1745
% tcp_server.socket.getsockname())
3094
1747
#service.interface = tcp_server.socket.getsockname()[3]
3098
# From the Avahi example code
3101
except dbus.exceptions.DBusException as error:
3102
logger.critical("D-Bus Exception", exc_info=error)
3105
# End of Avahi example code
1750
# From the Avahi example code
1753
except dbus.exceptions.DBusException, error:
1754
logger.critical(u"DBusException: %s", error)
1757
# End of Avahi example code
3107
1759
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3108
1760
lambda *args, **kwargs:
3109
1761
(tcp_server.handle_request
3110
1762
(*args[2:], **kwargs) or True))
3112
logger.debug("Starting main loop")
1764
logger.debug(u"Starting main loop")
3113
1765
main_loop.run()
3114
except AvahiError as error:
3115
logger.critical("Avahi Error", exc_info=error)
1766
except AvahiError, error:
1767
logger.critical(u"AvahiError: %s", error)
3118
1770
except KeyboardInterrupt:
3120
print("", file=sys.stderr)
3121
logger.debug("Server received KeyboardInterrupt")
3122
logger.debug("Server exiting")
1773
logger.debug(u"Server received KeyboardInterrupt")
1774
logger.debug(u"Server exiting")
3123
1775
# Must run before the D-Bus bus name gets deregistered
3127
1778
if __name__ == '__main__':