86
101
except ImportError:
87
102
SO_BINDTODEVICE = None
104
if sys.version_info.major == 2:
90
108
stored_state_file = "clients.pickle"
92
110
logger = logging.getLogger()
93
syslogger = (logging.handlers.SysLogHandler
94
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
95
address = str("/dev/log")))
98
if_nametoindex = (ctypes.cdll.LoadLibrary
99
(ctypes.util.find_library("c"))
114
if_nametoindex = ctypes.cdll.LoadLibrary(
115
ctypes.util.find_library("c")).if_nametoindex
101
116
except (OSError, AttributeError):
102
118
def if_nametoindex(interface):
103
119
"Get an interface index the hard way, i.e. using fcntl()"
104
120
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
105
121
with contextlib.closing(socket.socket()) as s:
106
122
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
107
struct.pack(str("16s16x"),
109
interface_index = struct.unpack(str("I"),
123
struct.pack(b"16s16x", interface))
124
interface_index = struct.unpack("I", ifreq[16:20])[0]
111
125
return interface_index
114
128
def initlogger(debug, level=logging.WARNING):
115
129
"""init logger and add loglevel"""
132
syslogger = (logging.handlers.SysLogHandler(
133
facility = logging.handlers.SysLogHandler.LOG_DAEMON,
134
address = "/dev/log"))
117
135
syslogger.setFormatter(logging.Formatter
118
136
('Mandos [%(process)d]: %(levelname)s:'
172
189
def password_encode(self, password):
173
190
# Passphrase can not be empty and can not contain newlines or
174
191
# NUL bytes. So we prefix it and hex encode it.
175
return b"mandos" + binascii.hexlify(password)
192
encoded = b"mandos" + binascii.hexlify(password)
193
if len(encoded) > 2048:
194
# GnuPG can't handle long passwords, so encode differently
195
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
196
.replace(b"\n", b"\\n")
197
.replace(b"\0", b"\\x00"))
177
200
def encrypt(self, data, password):
178
self.gnupg.passphrase = self.password_encode(password)
179
with open(os.devnull, "w") as devnull:
181
proc = self.gnupg.run(['--symmetric'],
182
create_fhs=['stdin', 'stdout'],
183
attach_fhs={'stderr': devnull})
184
with contextlib.closing(proc.handles['stdin']) as f:
186
with contextlib.closing(proc.handles['stdout']) as f:
187
ciphertext = f.read()
191
self.gnupg.passphrase = None
201
passphrase = self.password_encode(password)
202
with tempfile.NamedTemporaryFile(
203
dir=self.tempdir) as passfile:
204
passfile.write(passphrase)
206
proc = subprocess.Popen(['gpg', '--symmetric',
210
stdin = subprocess.PIPE,
211
stdout = subprocess.PIPE,
212
stderr = subprocess.PIPE)
213
ciphertext, err = proc.communicate(input = data)
214
if proc.returncode != 0:
192
216
return ciphertext
194
218
def decrypt(self, data, password):
195
self.gnupg.passphrase = self.password_encode(password)
196
with open(os.devnull, "w") as devnull:
198
proc = self.gnupg.run(['--decrypt'],
199
create_fhs=['stdin', 'stdout'],
200
attach_fhs={'stderr': devnull})
201
with contextlib.closing(proc.handles['stdin']) as f:
203
with contextlib.closing(proc.handles['stdout']) as f:
204
decrypted_plaintext = f.read()
208
self.gnupg.passphrase = None
219
passphrase = self.password_encode(password)
220
with tempfile.NamedTemporaryFile(
221
dir = self.tempdir) as passfile:
222
passfile.write(passphrase)
224
proc = subprocess.Popen(['gpg', '--decrypt',
228
stdin = subprocess.PIPE,
229
stdout = subprocess.PIPE,
230
stderr = subprocess.PIPE)
231
decrypted_plaintext, err = proc.communicate(input = data)
232
if proc.returncode != 0:
209
234
return decrypted_plaintext
212
237
class AvahiError(Exception):
213
238
def __init__(self, value, *args, **kwargs):
214
239
self.value = value
215
super(AvahiError, self).__init__(value, *args, **kwargs)
216
def __unicode__(self):
217
return unicode(repr(self.value))
240
return super(AvahiError, self).__init__(value, *args,
219
244
class AvahiServiceError(AvahiError):
222
248
class AvahiGroupError(AvahiError):
264
297
self.entry_group_state_changed_match = None
299
def rename(self, remove=True):
267
300
"""Derived from the Avahi example code"""
268
301
if self.rename_count >= self.max_renames:
269
302
logger.critical("No suitable Zeroconf service name found"
270
303
" after %i retries, exiting.",
271
304
self.rename_count)
272
305
raise AvahiServiceError("Too many renames")
273
self.name = unicode(self.server
274
.GetAlternativeServiceName(self.name))
307
self.server.GetAlternativeServiceName(self.name))
308
self.rename_count += 1
275
309
logger.info("Changing Zeroconf service name to %r ...",
280
315
except dbus.exceptions.DBusException as error:
281
logger.critical("D-Bus Exception", exc_info=error)
284
self.rename_count += 1
316
if (error.get_dbus_name()
317
== "org.freedesktop.Avahi.CollisionError"):
318
logger.info("Local Zeroconf service name collision.")
319
return self.rename(remove=False)
321
logger.critical("D-Bus Exception", exc_info=error)
286
325
def remove(self):
287
326
"""Derived from the Avahi example code"""
343
381
def server_state_changed(self, state, error=None):
344
382
"""Derived from the Avahi example code"""
345
383
logger.debug("Avahi server state change: %i", state)
346
bad_states = { avahi.SERVER_INVALID:
347
"Zeroconf server invalid",
348
avahi.SERVER_REGISTERING: None,
349
avahi.SERVER_COLLISION:
350
"Zeroconf server name collision",
351
avahi.SERVER_FAILURE:
352
"Zeroconf server failure" }
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",
353
390
if state in bad_states:
354
391
if bad_states[state] is not None:
355
392
if error is None:
374
422
follow_name_owner_changes=True),
375
423
avahi.DBUS_INTERFACE_SERVER)
376
424
self.server.connect_to_signal("StateChanged",
377
self.server_state_changed)
425
self.server_state_changed)
378
426
self.server_state_changed(self.server.GetState())
380
429
class AvahiServiceToSyslog(AvahiService):
430
def rename(self, *args, **kwargs):
382
431
"""Add the new name to the syslog messages"""
383
ret = AvahiService.rename(self)
384
syslogger.setFormatter(logging.Formatter
385
('Mandos ({0}) [%(process)d]:'
386
' %(levelname)s: %(message)s'
432
ret = AvahiService.rename(self, *args, **kwargs)
433
syslogger.setFormatter(logging.Formatter(
434
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
390
def timedelta_to_milliseconds(td):
391
"Convert a datetime.timedelta() to milliseconds"
392
return ((td.days * 24 * 60 * 60 * 1000)
393
+ (td.seconds * 1000)
394
+ (td.microseconds // 1000))
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))
396
448
class Client(object):
397
449
"""A representation of a client host served by this server.
434
488
runtime_expansions: Allowed attributes for runtime expansion.
435
489
expires: datetime.datetime(); time (UTC) when a client will be
436
490
disabled, or None
491
server_settings: The server_settings dict from main()
439
494
runtime_expansions = ("approval_delay", "approval_duration",
440
"created", "enabled", "fingerprint",
441
"host", "interval", "last_checked_ok",
495
"created", "enabled", "expires",
496
"fingerprint", "host", "interval",
497
"last_approval_request", "last_checked_ok",
442
498
"last_enabled", "name", "timeout")
443
client_defaults = { "timeout": "5m",
444
"extended_timeout": "15m",
446
"checker": "fping -q -- %%(host)s",
448
"approval_delay": "0s",
449
"approval_duration": "1s",
450
"approved_by_default": "True",
454
def timeout_milliseconds(self):
455
"Return the 'timeout' attribute in milliseconds"
456
return timedelta_to_milliseconds(self.timeout)
458
def extended_timeout_milliseconds(self):
459
"Return the 'extended_timeout' attribute in milliseconds"
460
return timedelta_to_milliseconds(self.extended_timeout)
462
def interval_milliseconds(self):
463
"Return the 'interval' attribute in milliseconds"
464
return timedelta_to_milliseconds(self.interval)
466
def approval_delay_milliseconds(self):
467
return timedelta_to_milliseconds(self.approval_delay)
501
"extended_timeout": "PT15M",
503
"checker": "fping -q -- %%(host)s",
505
"approval_delay": "PT0S",
506
"approval_duration": "PT1S",
507
"approved_by_default": "True",
470
512
def config_parser(config):
545
590
self.current_checker_command = None
546
591
self.approved = None
547
592
self.approvals_pending = 0
548
self.changedstate = (multiprocessing_manager
549
.Condition(multiprocessing_manager
551
self.client_structure = [attr for attr in
552
self.__dict__.iterkeys()
593
self.changedstate = multiprocessing_manager.Condition(
594
multiprocessing_manager.Lock())
595
self.client_structure = [attr
596
for attr in self.__dict__.iterkeys()
553
597
if not attr.startswith("_")]
554
598
self.client_structure.append("client_structure")
556
for name, t in inspect.getmembers(type(self),
600
for name, t in inspect.getmembers(
601
type(self), lambda obj: isinstance(obj, property)):
560
602
if not name.startswith("_"):
561
603
self.client_structure.append(name)
570
612
if getattr(self, "enabled", False):
571
613
# Already enabled
573
self.send_changedstate()
574
615
self.expires = datetime.datetime.utcnow() + self.timeout
575
616
self.enabled = True
576
617
self.last_enabled = datetime.datetime.utcnow()
577
618
self.init_checker()
619
self.send_changedstate()
579
621
def disable(self, quiet=True):
580
622
"""Disable this client."""
581
623
if not getattr(self, "enabled", False):
584
self.send_changedstate()
586
626
logger.info("Disabling client %s", self.name)
587
if getattr(self, "disable_initiator_tag", False):
627
if getattr(self, "disable_initiator_tag", None) is not None:
588
628
gobject.source_remove(self.disable_initiator_tag)
589
629
self.disable_initiator_tag = None
590
630
self.expires = None
591
if getattr(self, "checker_initiator_tag", False):
631
if getattr(self, "checker_initiator_tag", None) is not None:
592
632
gobject.source_remove(self.checker_initiator_tag)
593
633
self.checker_initiator_tag = None
594
634
self.stop_checker()
595
635
self.enabled = False
637
self.send_changedstate()
596
638
# Do not run this again if called by a gobject.timeout_add
602
644
def init_checker(self):
603
645
# Schedule a new checker to be started an 'interval' from now,
604
646
# and every interval from then on.
605
self.checker_initiator_tag = (gobject.timeout_add
606
(self.interval_milliseconds(),
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),
608
652
# Schedule a disable() when 'timeout' has passed
609
self.disable_initiator_tag = (gobject.timeout_add
610
(self.timeout_milliseconds(),
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)
612
657
# Also start a new checker *right now*.
613
658
self.start_checker()
615
def checker_callback(self, pid, condition, command):
660
def checker_callback(self, source, condition, connection,
616
662
"""The checker has completed, so take appropriate actions."""
617
663
self.checker_callback_tag = None
618
664
self.checker = None
619
if os.WIFEXITED(condition):
620
self.last_checker_status = os.WEXITSTATUS(condition)
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
621
672
if self.last_checker_status == 0:
622
673
logger.info("Checker for %(name)s succeeded",
624
675
self.checked_ok()
626
logger.info("Checker for %(name)s failed",
677
logger.info("Checker for %(name)s failed", vars(self))
629
679
self.last_checker_status = -1
680
self.last_checker_signal = -returncode
630
681
logger.warning("Checker for %(name)s crashed?",
633
685
def checked_ok(self):
634
686
"""Assert that the client has been seen, alive and well."""
635
687
self.last_checked_ok = datetime.datetime.utcnow()
636
688
self.last_checker_status = 0
689
self.last_checker_signal = None
637
690
self.bump_timeout()
639
692
def bump_timeout(self, timeout=None):
657
710
If a checker already exists, leave it running and do
659
712
# The reason for not killing a running checker is that if we
660
# did that, then if a checker (for some reason) started
661
# running slowly and taking more than 'interval' time, the
662
# client would inevitably timeout, since no checker would get
663
# a chance to run to completion. If we instead leave running
713
# did that, and if a checker (for some reason) started running
714
# slowly and taking more than 'interval' time, then the client
715
# would inevitably timeout, since no checker would get a
716
# chance to run to completion. If we instead leave running
664
717
# checkers alone, the checker would have to take more time
665
718
# than 'timeout' for the client to be disabled, which is as it
668
# If a checker exists, make sure it is not a zombie
670
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
671
except (AttributeError, OSError) as error:
672
if (isinstance(error, OSError)
673
and error.errno != errno.ECHILD):
677
logger.warning("Checker was a zombie")
678
gobject.source_remove(self.checker_callback_tag)
679
self.checker_callback(pid, status,
680
self.current_checker_command)
721
if self.checker is not None and not self.checker.is_alive():
722
logger.warning("Checker was not alive; joining")
681
725
# Start a new checker if needed
682
726
if self.checker is None:
727
# Escape attributes for the shell
729
attr: re.escape(str(getattr(self, attr)))
730
for attr in self.runtime_expansions }
684
# In case checker_command has exactly one % operator
685
command = self.checker_command % self.host
687
# Escape attributes for the shell
688
escaped_attrs = dict(
690
re.escape(unicode(str(getattr(self, attr, "")),
694
self.runtime_expansions)
697
command = self.checker_command % escaped_attrs
698
except TypeError as error:
699
logger.error('Could not format string "%s"',
700
self.checker_command, exc_info=error)
701
return True # Try again later
732
command = self.checker_command % escaped_attrs
733
except TypeError as error:
734
logger.error('Could not format string "%s"',
735
self.checker_command,
737
return True # Try again later
702
738
self.current_checker_command = command
704
logger.info("Starting checker %r for %s",
706
# We don't need to redirect stdout and stderr, since
707
# in normal mode, that is already done by daemon(),
708
# and in debug mode we don't want to. (Stdin is
709
# always replaced by /dev/null.)
710
self.checker = subprocess.Popen(command,
713
self.checker_callback_tag = (gobject.child_watch_add
715
self.checker_callback,
717
# The checker may have completed before the gobject
718
# watch was added. Check for this.
719
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
721
gobject.source_remove(self.checker_callback_tag)
722
self.checker_callback(pid, status, command)
723
except OSError as error:
724
logger.error("Failed to start subprocess",
739
logger.info("Starting checker %r for %s", command,
741
# We don't need to redirect stdout and stderr, since
742
# in normal mode, that is already done by daemon(),
743
# and in debug mode we don't want to. (Stdin is
744
# always replaced by /dev/null.)
745
# The exception is when not debugging but nevertheless
746
# running in the foreground; use the previously
748
popen_args = { "close_fds": True,
751
if (not self.server_settings["debug"]
752
and self.server_settings["foreground"]):
753
popen_args.update({"stdout": wnull,
755
pipe = multiprocessing.Pipe(duplex = False)
756
self.checker = multiprocessing.Process(
758
args = (pipe[1], subprocess.call, command),
761
self.checker_callback_tag = gobject.io_add_watch(
762
pipe[0].fileno(), gobject.IO_IN,
763
self.checker_callback, pipe[0], command)
726
764
# Re-run this periodically if run by gobject.timeout_add
798
835
"""Decorator to annotate D-Bus methods, signals or properties
838
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
839
"org.freedesktop.DBus.Property."
840
"EmitsChangedSignal": "false"})
801
841
@dbus_service_property("org.example.Interface", signature="b",
803
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
804
"org.freedesktop.DBus.Property."
805
"EmitsChangedSignal": "false"})
806
843
def Property_dbus_property(self):
807
844
return dbus.Boolean(False)
846
See also the DBusObjectWithAnnotations class.
809
849
def decorator(func):
810
850
func._dbus_annotations = annotations
815
856
class DBusPropertyException(dbus.exceptions.DBusException):
816
857
"""A base class for D-Bus property-related exceptions
818
def __unicode__(self):
819
return unicode(str(self))
822
862
class DBusPropertyAccessException(DBusPropertyException):
846
885
If called like _is_dbus_thing("method") it returns a function
847
886
suitable for use as predicate to inspect.getmembers().
849
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
888
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
852
891
def _get_all_dbus_things(self, thing):
853
892
"""Returns a generator of (name, attribute) pairs
855
return ((getattr(athing.__get__(self), "_dbus_name",
894
return ((getattr(athing.__get__(self), "_dbus_name", name),
857
895
athing.__get__(self))
858
896
for cls in self.__class__.__mro__
859
897
for name, athing in
860
inspect.getmembers(cls,
861
self._is_dbus_thing(thing)))
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):
964
"""A D-Bus object with properties.
966
Classes inheriting from this can use the dbus_service_property
967
decorator to expose methods as D-Bus properties. It exposes the
968
standard Get(), Set(), and GetAll() methods on the D-Bus.
863
971
def _get_dbus_property(self, interface_name, property_name):
864
972
"""Returns a bound method if one exists which is a D-Bus
865
973
property with the specified name and interface.
867
for cls in self.__class__.__mro__:
868
for name, value in (inspect.getmembers
870
self._is_dbus_thing("property"))):
975
for cls in self.__class__.__mro__:
976
for name, value in inspect.getmembers(
977
cls, self._is_dbus_thing("property")):
871
978
if (value._dbus_name == property_name
872
979
and value._dbus_interface == interface_name):
873
980
return value.__get__(self)
875
982
# No such property
876
raise DBusPropertyNotFound(self.dbus_object_path + ":"
877
+ interface_name + "."
880
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
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,
881
997
out_signature="v")
882
998
def Get(self, interface_name, property_name):
883
999
"""Standard D-Bus property Get() method, see D-Bus standard.
958
1088
if prop._dbus_interface
959
1089
== if_tag.getAttribute("name")):
960
1090
if_tag.appendChild(tag)
961
# Add annotation tags
962
for typ in ("method", "signal", "property"):
963
for tag in if_tag.getElementsByTagName(typ):
965
for name, prop in (self.
966
_get_all_dbus_things(typ)):
967
if (name == tag.getAttribute("name")
968
and prop._dbus_interface
969
== if_tag.getAttribute("name")):
970
annots.update(getattr
974
for name, value in annots.iteritems():
975
ann_tag = document.createElement(
977
ann_tag.setAttribute("name", name)
978
ann_tag.setAttribute("value", value)
979
tag.appendChild(ann_tag)
980
# Add interface annotation tags
981
for annotation, value in dict(
983
*(annotations().iteritems()
984
for name, annotations in
985
self._get_all_dbus_things("interface")
986
if name == if_tag.getAttribute("name")
988
ann_tag = document.createElement("annotation")
989
ann_tag.setAttribute("name", annotation)
990
ann_tag.setAttribute("value", value)
991
if_tag.appendChild(ann_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)
992
1107
# Add the names to the return values for the
993
1108
# "org.freedesktop.DBus.Properties" methods
994
1109
if (if_tag.getAttribute("name")
1012
1127
exc_info=error)
1013
1128
return xmlstring
1016
def datetime_to_dbus (dt, variant_level=0):
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):
1017
1200
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1019
1202
return dbus.String("", variant_level = variant_level)
1020
return dbus.String(dt.isoformat(),
1021
variant_level=variant_level)
1203
return dbus.String(dt.isoformat(), variant_level=variant_level)
1024
1206
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1054
1237
# Ignore non-D-Bus attributes, and D-Bus attributes
1055
1238
# with the wrong interface name
1056
1239
if (not hasattr(attribute, "_dbus_interface")
1057
or not attribute._dbus_interface
1058
.startswith(orig_interface_name)):
1240
or not attribute._dbus_interface.startswith(
1241
orig_interface_name)):
1060
1243
# Create an alternate D-Bus interface name based on
1061
1244
# the current name
1062
alt_interface = (attribute._dbus_interface
1063
.replace(orig_interface_name,
1064
alt_interface_name))
1245
alt_interface = attribute._dbus_interface.replace(
1246
orig_interface_name, alt_interface_name)
1065
1247
interface_names.add(alt_interface)
1066
1248
# Is this a D-Bus signal?
1067
1249
if getattr(attribute, "_dbus_is_signal", False):
1068
# Extract the original non-method function by
1070
nonmethod_func = (dict(
1250
if sys.version_info.major == 2:
1251
# Extract the original non-method undecorated
1252
# function by black magic
1253
nonmethod_func = (dict(
1071
1254
zip(attribute.func_code.co_freevars,
1072
attribute.__closure__))["func"]
1255
attribute.__closure__))
1256
["func"].cell_contents)
1258
nonmethod_func = attribute
1074
1259
# Create a new, but exactly alike, function
1075
1260
# object, and decorate it to be a new D-Bus signal
1076
1261
# with the alternate D-Bus interface name
1077
new_function = (dbus.service.signal
1079
attribute._dbus_signature)
1080
(types.FunctionType(
1081
nonmethod_func.func_code,
1082
nonmethod_func.func_globals,
1083
nonmethod_func.func_name,
1084
nonmethod_func.func_defaults,
1085
nonmethod_func.func_closure)))
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))
1086
1279
# Copy annotations, if any
1088
new_function._dbus_annotations = (
1089
dict(attribute._dbus_annotations))
1281
new_function._dbus_annotations = dict(
1282
attribute._dbus_annotations)
1090
1283
except AttributeError:
1092
1285
# Define a creator of a function to call both the
1112
1312
# object. Decorate it to be a new D-Bus method
1113
1313
# with the alternate D-Bus interface name. Add it
1114
1314
# to the class.
1115
attr[attrname] = (dbus.service.method
1117
attribute._dbus_in_signature,
1118
attribute._dbus_out_signature)
1120
(attribute.func_code,
1121
attribute.func_globals,
1122
attribute.func_name,
1123
attribute.func_defaults,
1124
attribute.func_closure)))
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)))
1125
1325
# Copy annotations, if any
1127
attr[attrname]._dbus_annotations = (
1128
dict(attribute._dbus_annotations))
1327
attr[attrname]._dbus_annotations = dict(
1328
attribute._dbus_annotations)
1129
1329
except AttributeError:
1131
1331
# Is this a D-Bus property?
1134
1334
# object, and decorate it to be a new D-Bus
1135
1335
# property with the alternate D-Bus interface
1136
1336
# name. Add it to the class.
1137
attr[attrname] = (dbus_service_property
1139
attribute._dbus_signature,
1140
attribute._dbus_access,
1142
._dbus_get_args_options
1145
(attribute.func_code,
1146
attribute.func_globals,
1147
attribute.func_name,
1148
attribute.func_defaults,
1149
attribute.func_closure)))
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)))
1150
1348
# Copy annotations, if any
1152
attr[attrname]._dbus_annotations = (
1153
dict(attribute._dbus_annotations))
1350
attr[attrname]._dbus_annotations = dict(
1351
attribute._dbus_annotations)
1154
1352
except AttributeError:
1156
1354
# Is this a D-Bus interface?
1210
1411
Client.__init__(self, *args, **kwargs)
1211
1412
# Only now, when this client is initialized, can it show up on
1213
client_object_name = unicode(self.name).translate(
1414
client_object_name = str(self.name).translate(
1214
1415
{ord("."): ord("_"),
1215
1416
ord("-"): ord("_")})
1216
self.dbus_object_path = (dbus.ObjectPath
1217
("/clients/" + client_object_name))
1417
self.dbus_object_path = dbus.ObjectPath(
1418
"/clients/" + client_object_name)
1218
1419
DBusObjectWithProperties.__init__(self, self.bus,
1219
1420
self.dbus_object_path)
1221
def notifychangeproperty(transform_func,
1222
dbus_name, type_func=lambda x: x,
1422
def notifychangeproperty(transform_func, dbus_name,
1423
type_func=lambda x: x,
1425
invalidate_only=False,
1426
_interface=_interface):
1224
1427
""" Modify a variable so that it's a property which announces
1225
1428
its changes to DBus.
1231
1434
to the D-Bus. Default: no transform
1232
1435
variant_level: D-Bus variant level. Default: 1
1234
attrname = "_{0}".format(dbus_name)
1437
attrname = "_{}".format(dbus_name)
1235
1439
def setter(self, value):
1236
1440
if hasattr(self, "dbus_object_path"):
1237
1441
if (not hasattr(self, attrname) or
1238
1442
type_func(getattr(self, attrname, None))
1239
1443
!= type_func(value)):
1240
dbus_value = transform_func(type_func(value),
1243
self.PropertyChanged(dbus.String(dbus_name),
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):
1245
1459
setattr(self, attrname, value)
1247
1461
return property(lambda self: getattr(self, attrname), setter)
1264
1478
datetime_to_dbus, "LastApprovalRequest")
1265
1479
approved_by_default = notifychangeproperty(dbus.Boolean,
1266
1480
"ApprovedByDefault")
1267
approval_delay = notifychangeproperty(dbus.UInt64,
1270
timedelta_to_milliseconds)
1481
approval_delay = notifychangeproperty(
1482
dbus.UInt64, "ApprovalDelay",
1483
type_func = lambda td: td.total_seconds() * 1000)
1271
1484
approval_duration = notifychangeproperty(
1272
1485
dbus.UInt64, "ApprovalDuration",
1273
type_func = timedelta_to_milliseconds)
1486
type_func = lambda td: td.total_seconds() * 1000)
1274
1487
host = notifychangeproperty(dbus.String, "Host")
1275
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1277
timedelta_to_milliseconds)
1488
timeout = notifychangeproperty(
1489
dbus.UInt64, "Timeout",
1490
type_func = lambda td: td.total_seconds() * 1000)
1278
1491
extended_timeout = notifychangeproperty(
1279
1492
dbus.UInt64, "ExtendedTimeout",
1280
type_func = timedelta_to_milliseconds)
1281
interval = notifychangeproperty(dbus.UInt64,
1284
timedelta_to_milliseconds)
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)
1285
1497
checker_command = notifychangeproperty(dbus.String, "Checker")
1498
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1499
invalidate_only=True)
1287
1501
del notifychangeproperty
1295
1509
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1296
1510
Client.__del__(self, *args, **kwargs)
1298
def checker_callback(self, pid, condition, command,
1300
self.checker_callback_tag = None
1302
if os.WIFEXITED(condition):
1303
exitstatus = os.WEXITSTATUS(condition)
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
1304
1519
# Emit D-Bus signal
1305
1520
self.CheckerCompleted(dbus.Int16(exitstatus),
1306
dbus.Int64(condition),
1521
# This is specific to GNU libC
1522
dbus.Int64(exitstatus << 8),
1307
1523
dbus.String(command))
1309
1525
# Emit D-Bus signal
1310
1526
self.CheckerCompleted(dbus.Int16(-1),
1311
dbus.Int64(condition),
1528
# This is specific to GNU libC
1530
| self.last_checker_signal),
1312
1531
dbus.String(command))
1314
return Client.checker_callback(self, pid, condition, command,
1317
1534
def start_checker(self, *args, **kwargs):
1318
old_checker = self.checker
1319
if self.checker is not None:
1320
old_checker_pid = self.checker.pid
1322
old_checker_pid = None
1535
old_checker_pid = getattr(self.checker, "pid", None)
1323
1536
r = Client.start_checker(self, *args, **kwargs)
1324
1537
# Only if new checker process was started
1325
1538
if (self.checker is not None
1441
1653
self.approved_by_default = bool(value)
1443
1655
# ApprovalDelay - property
1444
@dbus_service_property(_interface, signature="t",
1656
@dbus_service_property(_interface,
1445
1658
access="readwrite")
1446
1659
def ApprovalDelay_dbus_property(self, value=None):
1447
1660
if value is None: # get
1448
return dbus.UInt64(self.approval_delay_milliseconds())
1661
return dbus.UInt64(self.approval_delay.total_seconds()
1449
1663
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1451
1665
# ApprovalDuration - property
1452
@dbus_service_property(_interface, signature="t",
1666
@dbus_service_property(_interface,
1453
1668
access="readwrite")
1454
1669
def ApprovalDuration_dbus_property(self, value=None):
1455
1670
if value is None: # get
1456
return dbus.UInt64(timedelta_to_milliseconds(
1457
self.approval_duration))
1671
return dbus.UInt64(self.approval_duration.total_seconds()
1458
1673
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1460
1675
# Name - property
1677
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1461
1678
@dbus_service_property(_interface, signature="s", access="read")
1462
1679
def Name_dbus_property(self):
1463
1680
return dbus.String(self.name)
1465
1682
# Fingerprint - property
1684
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1466
1685
@dbus_service_property(_interface, signature="s", access="read")
1467
1686
def Fingerprint_dbus_property(self):
1468
1687
return dbus.String(self.fingerprint)
1470
1689
# Host - property
1471
@dbus_service_property(_interface, signature="s",
1690
@dbus_service_property(_interface,
1472
1692
access="readwrite")
1473
1693
def Host_dbus_property(self, value=None):
1474
1694
if value is None: # get
1475
1695
return dbus.String(self.host)
1476
self.host = unicode(value)
1696
self.host = str(value)
1478
1698
# Created - property
1700
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1479
1701
@dbus_service_property(_interface, signature="s", access="read")
1480
1702
def Created_dbus_property(self):
1481
1703
return datetime_to_dbus(self.created)
1522
1745
return datetime_to_dbus(self.last_approval_request)
1524
1747
# Timeout - property
1525
@dbus_service_property(_interface, signature="t",
1748
@dbus_service_property(_interface,
1526
1750
access="readwrite")
1527
1751
def Timeout_dbus_property(self, value=None):
1528
1752
if value is None: # get
1529
return dbus.UInt64(self.timeout_milliseconds())
1753
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1754
old_timeout = self.timeout
1530
1755
self.timeout = datetime.timedelta(0, 0, 0, value)
1531
# Reschedule timeout
1756
# Reschedule disabling
1532
1757
if self.enabled:
1533
1758
now = datetime.datetime.utcnow()
1534
time_to_die = timedelta_to_milliseconds(
1535
(self.last_checked_ok + self.timeout) - now)
1536
if time_to_die <= 0:
1759
self.expires += self.timeout - old_timeout
1760
if self.expires <= now:
1537
1761
# The timeout has passed
1540
self.expires = (now +
1541
datetime.timedelta(milliseconds =
1543
1764
if (getattr(self, "disable_initiator_tag", None)
1546
1767
gobject.source_remove(self.disable_initiator_tag)
1547
self.disable_initiator_tag = (gobject.timeout_add
1768
self.disable_initiator_tag = gobject.timeout_add(
1769
int((self.expires - now).total_seconds() * 1000),
1551
1772
# ExtendedTimeout - property
1552
@dbus_service_property(_interface, signature="t",
1773
@dbus_service_property(_interface,
1553
1775
access="readwrite")
1554
1776
def ExtendedTimeout_dbus_property(self, value=None):
1555
1777
if value is None: # get
1556
return dbus.UInt64(self.extended_timeout_milliseconds())
1778
return dbus.UInt64(self.extended_timeout.total_seconds()
1557
1780
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1559
1782
# Interval - property
1560
@dbus_service_property(_interface, signature="t",
1783
@dbus_service_property(_interface,
1561
1785
access="readwrite")
1562
1786
def Interval_dbus_property(self, value=None):
1563
1787
if value is None: # get
1564
return dbus.UInt64(self.interval_milliseconds())
1788
return dbus.UInt64(self.interval.total_seconds() * 1000)
1565
1789
self.interval = datetime.timedelta(0, 0, 0, value)
1566
1790
if getattr(self, "checker_initiator_tag", None) is None:
1568
1792
if self.enabled:
1569
1793
# Reschedule checker run
1570
1794
gobject.source_remove(self.checker_initiator_tag)
1571
self.checker_initiator_tag = (gobject.timeout_add
1572
(value, self.start_checker))
1573
self.start_checker() # Start one now, too
1795
self.checker_initiator_tag = gobject.timeout_add(
1796
value, self.start_checker)
1797
self.start_checker() # Start one now, too
1575
1799
# Checker - property
1576
@dbus_service_property(_interface, signature="s",
1800
@dbus_service_property(_interface,
1577
1802
access="readwrite")
1578
1803
def Checker_dbus_property(self, value=None):
1579
1804
if value is None: # get
1580
1805
return dbus.String(self.checker_command)
1581
self.checker_command = unicode(value)
1806
self.checker_command = str(value)
1583
1808
# CheckerRunning - property
1584
@dbus_service_property(_interface, signature="b",
1809
@dbus_service_property(_interface,
1585
1811
access="readwrite")
1586
1812
def CheckerRunning_dbus_property(self, value=None):
1587
1813
if value is None: # get
1813
2044
def fingerprint(openpgp):
1814
2045
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1815
2046
# New GnuTLS "datum" with the OpenPGP public key
1816
datum = (gnutls.library.types
1817
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1820
ctypes.c_uint(len(openpgp))))
2047
datum = gnutls.library.types.gnutls_datum_t(
2048
ctypes.cast(ctypes.c_char_p(openpgp),
2049
ctypes.POINTER(ctypes.c_ubyte)),
2050
ctypes.c_uint(len(openpgp)))
1821
2051
# New empty GnuTLS certificate
1822
2052
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1823
(gnutls.library.functions
1824
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
2053
gnutls.library.functions.gnutls_openpgp_crt_init(
1825
2055
# Import the OpenPGP public key into the certificate
1826
(gnutls.library.functions
1827
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1828
gnutls.library.constants
1829
.GNUTLS_OPENPGP_FMT_RAW))
2056
gnutls.library.functions.gnutls_openpgp_crt_import(
2057
crt, ctypes.byref(datum),
2058
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1830
2059
# Verify the self signature in the key
1831
2060
crtverify = ctypes.c_uint()
1832
(gnutls.library.functions
1833
.gnutls_openpgp_crt_verify_self(crt, 0,
1834
ctypes.byref(crtverify)))
2061
gnutls.library.functions.gnutls_openpgp_crt_verify_self(
2062
crt, 0, ctypes.byref(crtverify))
1835
2063
if crtverify.value != 0:
1836
2064
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1837
raise (gnutls.errors.CertificateSecurityError
2065
raise gnutls.errors.CertificateSecurityError(
1839
2067
# New buffer for the fingerprint
1840
2068
buf = ctypes.create_string_buffer(20)
1841
2069
buf_len = ctypes.c_size_t()
1842
2070
# Get the fingerprint from the certificate into the buffer
1843
(gnutls.library.functions
1844
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1845
ctypes.byref(buf_len)))
2071
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
2072
crt, ctypes.byref(buf), ctypes.byref(buf_len))
1846
2073
# Deinit the certificate
1847
2074
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1848
2075
# Convert the buffer to a Python bytestring
1898
2126
interface: None or a network interface name (string)
1899
2127
use_ipv6: Boolean; to use IPv6 or not
1901
2130
def __init__(self, server_address, RequestHandlerClass,
1902
interface=None, use_ipv6=True):
2134
"""If socketfd is set, use that file descriptor instead of
2135
creating a new one with socket.socket().
1903
2137
self.interface = interface
1905
2139
self.address_family = socket.AF_INET6
2140
if socketfd is not None:
2141
# Save the file descriptor
2142
self.socketfd = socketfd
2143
# Save the original socket.socket() function
2144
self.socket_socket = socket.socket
2145
# To implement --socket, we monkey patch socket.socket.
2147
# (When socketserver.TCPServer is a new-style class, we
2148
# could make self.socket into a property instead of monkey
2149
# patching socket.socket.)
2151
# Create a one-time-only replacement for socket.socket()
2152
@functools.wraps(socket.socket)
2153
def socket_wrapper(*args, **kwargs):
2154
# Restore original function so subsequent calls are
2156
socket.socket = self.socket_socket
2157
del self.socket_socket
2158
# This time only, return a new socket object from the
2159
# saved file descriptor.
2160
return socket.fromfd(self.socketfd, *args, **kwargs)
2161
# Replace socket.socket() function with wrapper
2162
socket.socket = socket_wrapper
2163
# The socketserver.TCPServer.__init__ will call
2164
# socket.socket(), which might be our replacement,
2165
# socket_wrapper(), if socketfd was set.
1906
2166
socketserver.TCPServer.__init__(self, server_address,
1907
2167
RequestHandlerClass)
1908
2169
def server_bind(self):
1909
2170
"""This overrides the normal server_bind() function
1910
2171
to bind to an interface if one was specified, and also NOT to
1916
2177
self.interface)
1919
self.socket.setsockopt(socket.SOL_SOCKET,
2180
self.socket.setsockopt(
2181
socket.SOL_SOCKET, SO_BINDTODEVICE,
2182
(self.interface + "\0").encode("utf-8"))
1923
2183
except socket.error as error:
1924
if error[0] == errno.EPERM:
1925
logger.error("No permission to"
1926
" bind to interface %s",
1928
elif error[0] == errno.ENOPROTOOPT:
2184
if error.errno == errno.EPERM:
2185
logger.error("No permission to bind to"
2186
" interface %s", self.interface)
2187
elif error.errno == errno.ENOPROTOOPT:
1929
2188
logger.error("SO_BINDTODEVICE not available;"
1930
2189
" cannot bind to interface %s",
1931
2190
self.interface)
2191
elif error.errno == errno.ENODEV:
2192
logger.error("Interface %s does not exist,"
2193
" cannot bind", self.interface)
1934
2196
# Only bind(2) the socket if we really need to.
1985
2253
def add_pipe(self, parent_pipe, proc):
1986
2254
# Call "handle_ipc" for both data and EOF events
1987
gobject.io_add_watch(parent_pipe.fileno(),
1988
gobject.IO_IN | gobject.IO_HUP,
1989
functools.partial(self.handle_ipc,
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,
1994
def handle_ipc(self, source, condition, parent_pipe=None,
1995
proc = None, client_object=None):
1997
gobject.IO_IN: "IN", # There is data to read.
1998
gobject.IO_OUT: "OUT", # Data can be written (without
2000
gobject.IO_PRI: "PRI", # There is urgent data to read.
2001
gobject.IO_ERR: "ERR", # Error condition.
2002
gobject.IO_HUP: "HUP" # Hung up (the connection has been
2003
# broken, usually for pipes and
2006
conditions_string = ' | '.join(name
2008
condition_names.iteritems()
2009
if cond & condition)
2262
def handle_ipc(self, source, condition,
2265
client_object=None):
2010
2266
# error, or the other end of multiprocessing.Pipe has closed
2011
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
2267
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2012
2268
# Wait for other process to exit
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}"
2075
2427
def string_to_delta(interval):
2076
2428
"""Parse a string and return a datetime.timedelta
2170
2527
parser.add_argument("--no-dbus", action="store_false",
2171
2528
dest="use_dbus", help="Do not provide D-Bus"
2172
" system bus interface")
2529
" system bus interface", default=None)
2173
2530
parser.add_argument("--no-ipv6", action="store_false",
2174
dest="use_ipv6", help="Do not use IPv6")
2531
dest="use_ipv6", help="Do not use IPv6",
2175
2533
parser.add_argument("--no-restore", action="store_false",
2176
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")
2178
2539
parser.add_argument("--statedir", metavar="DIR",
2179
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",
2181
2547
options = parser.parse_args()
2183
2549
if options.check:
2551
fail_count, test_count = doctest.testmod()
2552
sys.exit(os.EX_OK if fail_count == 0 else 1)
2188
2554
# Default values for config file for server-global settings
2189
2555
server_defaults = { "interface": "",
2192
2558
"debug": "False",
2194
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2560
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2561
":+SIGN-DSA-SHA256",
2195
2562
"servicename": "Mandos",
2196
2563
"use_dbus": "True",
2197
2564
"use_ipv6": "True",
2198
2565
"debuglevel": "",
2199
2566
"restore": "True",
2200
"statedir": "/var/lib/mandos"
2568
"statedir": "/var/lib/mandos",
2569
"foreground": "False",
2203
2573
# Parse config file for server-global settings
2204
2574
server_config = configparser.SafeConfigParser(server_defaults)
2205
2575
del server_defaults
2206
server_config.read(os.path.join(options.configdir,
2576
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2208
2577
# Convert the SafeConfigParser object to a dict
2209
2578
server_settings = server_config.defaults()
2210
2579
# Use the appropriate methods on the non-string config options
2211
for option in ("debug", "use_dbus", "use_ipv6"):
2580
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2212
2581
server_settings[option] = server_config.getboolean("DEFAULT",
2214
2583
if server_settings["port"]:
2215
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
2217
2595
del server_config
2219
2597
# Override the settings from the config file with command line
2220
2598
# options, if set.
2221
2599
for option in ("interface", "address", "port", "debug",
2222
"priority", "servicename", "configdir",
2223
"use_dbus", "use_ipv6", "debuglevel", "restore",
2600
"priority", "servicename", "configdir", "use_dbus",
2601
"use_ipv6", "debuglevel", "restore", "statedir",
2602
"socket", "foreground", "zeroconf"):
2225
2603
value = getattr(options, option)
2226
2604
if value is not None:
2227
2605
server_settings[option] = value
2229
2607
# Force all strings to be unicode
2230
2608
for option in server_settings.keys():
2231
if type(server_settings[option]) is str:
2232
server_settings[option] = unicode(server_settings[option])
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
2233
2619
# Now we have our good server settings in "server_settings"
2235
2621
##################################################################
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")
2237
2628
# For convenience
2238
2629
debug = server_settings["debug"]
2239
2630
debuglevel = server_settings["debuglevel"]
2267
2659
global mandos_dbus_service
2268
2660
mandos_dbus_service = None
2270
tcp_server = MandosServer((server_settings["address"],
2271
server_settings["port"]),
2273
interface=(server_settings["interface"]
2277
server_settings["priority"],
2280
pidfilename = "/var/run/mandos.pid"
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"
2282
pidfile = open(pidfilename, "w")
2679
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2283
2680
except IOError as e:
2284
2681
logger.error("Could not open file %r", pidfilename,
2339
2738
bus_name = dbus.service.BusName("se.recompile.Mandos",
2340
bus, do_not_queue=True)
2341
old_bus_name = (dbus.service.BusName
2342
("se.bsnet.fukt.Mandos", bus,
2344
except dbus.exceptions.NameExistsException as e:
2741
old_bus_name = dbus.service.BusName(
2742
"se.bsnet.fukt.Mandos", bus,
2744
except dbus.exceptions.DBusException as e:
2345
2745
logger.error("Disabling D-Bus:", exc_info=e)
2346
2746
use_dbus = False
2347
2747
server_settings["use_dbus"] = False
2348
2748
tcp_server.use_dbus = False
2349
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2350
service = AvahiServiceToSyslog(name =
2351
server_settings["servicename"],
2352
servicetype = "_mandos._tcp",
2353
protocol = protocol, bus = bus)
2354
if server_settings["interface"]:
2355
service.interface = (if_nametoindex
2356
(str(server_settings["interface"])))
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"))
2358
2760
global multiprocessing_manager
2359
2761
multiprocessing_manager = multiprocessing.Manager()
2366
2768
old_client_settings = {}
2367
2769
clients_data = {}
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:
2369
2779
# Get client data and settings from last running state.
2370
2780
if server_settings["restore"]:
2372
2782
with open(stored_state_path, "rb") as stored_state:
2373
clients_data, old_client_settings = (pickle.load
2783
clients_data, old_client_settings = pickle.load(
2375
2785
os.remove(stored_state_path)
2376
2786
except IOError as e:
2377
2787
if e.errno == errno.ENOENT:
2378
logger.warning("Could not load persistent state: {0}"
2379
.format(os.strerror(e.errno)))
2788
logger.warning("Could not load persistent state:"
2789
" {}".format(os.strerror(e.errno)))
2381
2791
logger.critical("Could not load persistent state:",
2384
2794
except EOFError as e:
2385
2795
logger.warning("Could not load persistent state: "
2386
"EOFError:", exc_info=e)
2388
2799
with PGPEngine() as pgp:
2389
for client_name, client in clients_data.iteritems():
2800
for client_name, client in clients_data.items():
2801
# Skip removed clients
2802
if client_name not in client_settings:
2390
2805
# Decide which value to use after restoring saved state.
2391
2806
# We have three different values: Old config file,
2392
2807
# new config file, and saved state.
2413
2828
if datetime.datetime.utcnow() >= client["expires"]:
2414
2829
if not client["last_checked_ok"]:
2415
2830
logger.warning(
2416
"disabling client {0} - Client never "
2417
"performed a successful checker"
2418
.format(client_name))
2831
"disabling client {} - Client never "
2832
"performed a successful checker".format(
2419
2834
client["enabled"] = False
2420
2835
elif client["last_checker_status"] != 0:
2421
2836
logger.warning(
2422
"disabling client {0} - Client "
2423
"last checker failed with error code {1}"
2424
.format(client_name,
2425
client["last_checker_status"]))
2837
"disabling client {} - Client last"
2838
" checker failed with error code"
2841
client["last_checker_status"]))
2426
2842
client["enabled"] = False
2428
client["expires"] = (datetime.datetime
2430
+ client["timeout"])
2844
client["expires"] = (
2845
datetime.datetime.utcnow()
2846
+ client["timeout"])
2431
2847
logger.debug("Last checker succeeded,"
2432
" keeping {0} enabled"
2433
.format(client_name))
2848
" keeping {} enabled".format(
2435
client["secret"] = (
2436
pgp.decrypt(client["encrypted_secret"],
2437
client_settings[client_name]
2851
client["secret"] = pgp.decrypt(
2852
client["encrypted_secret"],
2853
client_settings[client_name]["secret"])
2439
2854
except PGPError:
2440
2855
# If decryption fails, we use secret from new settings
2441
logger.debug("Failed to decrypt {0} old secret"
2442
.format(client_name))
2443
client["secret"] = (
2444
client_settings[client_name]["secret"])
2856
logger.debug("Failed to decrypt {} old secret".format(
2858
client["secret"] = (client_settings[client_name]
2446
2861
# Add/remove clients based on new changes made to config
2447
2862
for client_name in (set(old_client_settings)
2452
2867
clients_data[client_name] = client_settings[client_name]
2454
2869
# Create all client objects
2455
for client_name, client in clients_data.iteritems():
2870
for client_name, client in clients_data.items():
2456
2871
tcp_server.clients[client_name] = client_class(
2457
name = client_name, settings = client)
2874
server_settings = server_settings)
2459
2876
if not tcp_server.clients:
2460
2877
logger.warning("No clients defined")
2466
pidfile.write(str(pid) + "\n".encode("utf-8"))
2469
logger.error("Could not write to file %r with PID %d",
2472
# "pidfile" was never created
2880
if pidfile is not None:
2884
print(pid, file=pidfile)
2886
logger.error("Could not write to file %r with PID %d",
2474
2889
del pidfilename
2475
signal.signal(signal.SIGINT, signal.SIG_IGN)
2477
2891
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2478
2892
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2481
@alternate_dbus_interfaces({"se.recompile.Mandos":
2482
"se.bsnet.fukt.Mandos"})
2483
class MandosDBusService(DBusObjectWithProperties):
2896
@alternate_dbus_interfaces(
2897
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2898
class MandosDBusService(DBusObjectWithObjectManager):
2484
2899
"""A D-Bus proxy object"""
2485
2901
def __init__(self):
2486
2902
dbus.service.Object.__init__(self, bus, "/")
2487
2904
_interface = "se.recompile.Mandos"
2489
@dbus_interface_annotations(_interface)
2491
return { "org.freedesktop.DBus.Property"
2492
".EmitsChangedSignal":
2495
2906
@dbus.service.signal(_interface, signature="o")
2496
2907
def ClientAdded(self, objpath):
2916
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2505
2918
@dbus.service.signal(_interface, signature="os")
2506
2919
def ClientRemoved(self, objpath, name):
2923
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2510
2925
@dbus.service.method(_interface, out_signature="ao")
2511
2926
def GetAllClients(self):
2513
return dbus.Array(c.dbus_object_path
2928
return dbus.Array(c.dbus_object_path for c in
2515
2929
tcp_server.clients.itervalues())
2931
@dbus_annotations({"org.freedesktop.DBus.Deprecated":
2517
2933
@dbus.service.method(_interface,
2518
2934
out_signature="a{oa{sv}}")
2519
2935
def GetAllClientsWithProperties(self):
2521
2937
return dbus.Dictionary(
2522
((c.dbus_object_path, c.GetAll(""))
2523
for c in tcp_server.clients.itervalues()),
2938
{ c.dbus_object_path: c.GetAll(
2939
"se.recompile.Mandos.Client")
2940
for c in tcp_server.clients.itervalues() },
2524
2941
signature="oa{sv}")
2526
2943
@dbus.service.method(_interface, in_signature="o")
2530
2947
if c.dbus_object_path == object_path:
2531
2948
del tcp_server.clients[c.name]
2532
2949
c.remove_from_connection()
2533
# Don't signal anything except ClientRemoved
2950
# Don't signal the disabling
2534
2951
c.disable(quiet=True)
2536
self.ClientRemoved(object_path, c.name)
2952
# Emit D-Bus signal for removal
2953
self.client_removed_signal(c)
2538
2955
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,
2542
2995
mandos_dbus_service = MandosDBusService()
2545
2998
"Cleanup function; run on exit"
2548
3002
multiprocessing.active_children()
2549
3004
if not (tcp_server.clients or client_settings):
2592
3049
except NameError:
2594
3051
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2595
logger.warning("Could not save persistent state: {0}"
3052
logger.warning("Could not save persistent state: {}"
2596
3053
.format(os.strerror(e.errno)))
2598
3055
logger.warning("Could not save persistent state:",
2602
3059
# Delete all clients, and settings from config
2603
3060
while tcp_server.clients:
2604
3061
name, client = tcp_server.clients.popitem()
2606
3063
client.remove_from_connection()
2607
# Don't signal anything except ClientRemoved
3064
# Don't signal the disabling
2608
3065
client.disable(quiet=True)
2611
mandos_dbus_service.ClientRemoved(client
3066
# Emit D-Bus signal for removal
3067
mandos_dbus_service.client_removed_signal(client)
2614
3068
client_settings.clear()
2616
3070
atexit.register(cleanup)
2618
3072
for client in tcp_server.clients.itervalues():
2621
mandos_dbus_service.ClientAdded(client.dbus_object_path)
3074
# Emit D-Bus signal for adding
3075
mandos_dbus_service.client_added_signal(client)
2622
3076
# Need to initiate checking of clients
2623
3077
if client.enabled:
2624
3078
client.init_checker()