81
100
except ImportError:
82
101
SO_BINDTODEVICE = None
87
#logger = logging.getLogger('mandos')
88
logger = logging.Logger('mandos')
89
syslogger = (logging.handlers.SysLogHandler
90
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
91
address = str("/dev/log")))
92
syslogger.setFormatter(logging.Formatter
93
('Mandos [%(process)d]: %(levelname)s:'
95
logger.addHandler(syslogger)
97
console = logging.StreamHandler()
98
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
101
logger.addHandler(console)
103
if sys.version_info.major == 2:
107
stored_state_file = "clients.pickle"
109
logger = logging.getLogger()
113
if_nametoindex = ctypes.cdll.LoadLibrary(
114
ctypes.util.find_library("c")).if_nametoindex
115
except (OSError, AttributeError):
117
def if_nametoindex(interface):
118
"Get an interface index the hard way, i.e. using fcntl()"
119
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
120
with contextlib.closing(socket.socket()) as s:
121
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
122
struct.pack(b"16s16x", interface))
123
interface_index = struct.unpack("I", ifreq[16:20])[0]
124
return interface_index
127
def initlogger(debug, level=logging.WARNING):
128
"""init logger and add loglevel"""
131
syslogger = (logging.handlers.SysLogHandler(
132
facility = logging.handlers.SysLogHandler.LOG_DAEMON,
133
address = "/dev/log"))
134
syslogger.setFormatter(logging.Formatter
135
('Mandos [%(process)d]: %(levelname)s:'
137
logger.addHandler(syslogger)
140
console = logging.StreamHandler()
141
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
145
logger.addHandler(console)
146
logger.setLevel(level)
149
class PGPError(Exception):
150
"""Exception if encryption/decryption fails"""
154
class PGPEngine(object):
155
"""A simple class for OpenPGP symmetric encryption & decryption"""
158
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
159
self.gnupgargs = ['--batch',
160
'--home', self.tempdir,
168
def __exit__(self, exc_type, exc_value, traceback):
176
if self.tempdir is not None:
177
# Delete contents of tempdir
178
for root, dirs, files in os.walk(self.tempdir,
180
for filename in files:
181
os.remove(os.path.join(root, filename))
183
os.rmdir(os.path.join(root, dirname))
185
os.rmdir(self.tempdir)
188
def password_encode(self, password):
189
# Passphrase can not be empty and can not contain newlines or
190
# NUL bytes. So we prefix it and hex encode it.
191
encoded = b"mandos" + binascii.hexlify(password)
192
if len(encoded) > 2048:
193
# GnuPG can't handle long passwords, so encode differently
194
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
195
.replace(b"\n", b"\\n")
196
.replace(b"\0", b"\\x00"))
199
def encrypt(self, data, password):
200
passphrase = self.password_encode(password)
201
with tempfile.NamedTemporaryFile(
202
dir=self.tempdir) as passfile:
203
passfile.write(passphrase)
205
proc = subprocess.Popen(['gpg', '--symmetric',
209
stdin = subprocess.PIPE,
210
stdout = subprocess.PIPE,
211
stderr = subprocess.PIPE)
212
ciphertext, err = proc.communicate(input = data)
213
if proc.returncode != 0:
217
def decrypt(self, data, password):
218
passphrase = self.password_encode(password)
219
with tempfile.NamedTemporaryFile(
220
dir = self.tempdir) as passfile:
221
passfile.write(passphrase)
223
proc = subprocess.Popen(['gpg', '--decrypt',
227
stdin = subprocess.PIPE,
228
stdout = subprocess.PIPE,
229
stderr = subprocess.PIPE)
230
decrypted_plaintext, err = proc.communicate(input = data)
231
if proc.returncode != 0:
233
return decrypted_plaintext
103
236
class AvahiError(Exception):
104
237
def __init__(self, value, *args, **kwargs):
105
238
self.value = value
106
super(AvahiError, self).__init__(value, *args, **kwargs)
107
def __unicode__(self):
108
return unicode(repr(self.value))
239
return super(AvahiError, self).__init__(value, *args,
110
243
class AvahiServiceError(AvahiError):
113
247
class AvahiGroupError(AvahiError):
210
364
elif state == avahi.ENTRY_GROUP_FAILURE:
211
365
logger.critical("Avahi: Error in group state changed %s",
213
raise AvahiGroupError("State changed: %s"
367
raise AvahiGroupError("State changed: {!s}".format(error))
215
369
def cleanup(self):
216
370
"""Derived from the Avahi example code"""
217
371
if self.group is not None:
374
except (dbus.exceptions.UnknownMethodException,
375
dbus.exceptions.DBusException):
219
377
self.group = None
220
def server_state_changed(self, state):
380
def server_state_changed(self, state, error=None):
221
381
"""Derived from the Avahi example code"""
222
382
logger.debug("Avahi server state change: %i", state)
223
if state == avahi.SERVER_COLLISION:
224
logger.error("Zeroconf server name collision")
384
avahi.SERVER_INVALID: "Zeroconf server invalid",
385
avahi.SERVER_REGISTERING: None,
386
avahi.SERVER_COLLISION: "Zeroconf server name collision",
387
avahi.SERVER_FAILURE: "Zeroconf server failure",
389
if state in bad_states:
390
if bad_states[state] is not None:
392
logger.error(bad_states[state])
394
logger.error(bad_states[state] + ": %r", error)
226
396
elif state == avahi.SERVER_RUNNING:
400
logger.debug("Unknown state: %r", state)
402
logger.debug("Unknown state: %r: %r", state, error)
228
404
def activate(self):
229
405
"""Derived from the Avahi example code"""
230
406
if self.server is None:
231
407
self.server = dbus.Interface(
232
408
self.bus.get_object(avahi.DBUS_NAME,
233
avahi.DBUS_PATH_SERVER),
409
avahi.DBUS_PATH_SERVER,
410
follow_name_owner_changes=True),
234
411
avahi.DBUS_INTERFACE_SERVER)
235
412
self.server.connect_to_signal("StateChanged",
236
self.server_state_changed)
413
self.server_state_changed)
237
414
self.server_state_changed(self.server.GetState())
417
class AvahiServiceToSyslog(AvahiService):
418
def rename(self, *args, **kwargs):
419
"""Add the new name to the syslog messages"""
420
ret = AvahiService.rename(self, *args, **kwargs)
421
syslogger.setFormatter(logging.Formatter(
422
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
240
427
class Client(object):
241
428
"""A representation of a client host served by this server.
244
_approved: bool(); 'None' if not yet approved/disapproved
431
approved: bool(); 'None' if not yet approved/disapproved
245
432
approval_delay: datetime.timedelta(); Time to wait for approval
246
433
approval_duration: datetime.timedelta(); Duration of one approval
247
434
checker: subprocess.Popen(); a running checker process used
264
452
interval: datetime.timedelta(); How often to start a new checker
265
453
last_approval_request: datetime.datetime(); (UTC) or None
266
454
last_checked_ok: datetime.datetime(); (UTC) or None
267
last_enabled: datetime.datetime(); (UTC)
455
last_checker_status: integer between 0 and 255 reflecting exit
456
status of last checker. -1 reflects crashed
457
checker, -2 means no checker completed yet.
458
last_enabled: datetime.datetime(); (UTC) or None
268
459
name: string; from the config file, used in log messages and
269
460
D-Bus identifiers
270
461
secret: bytestring; sent verbatim (over TLS) to client
271
462
timeout: datetime.timedelta(); How long from last_checked_ok
272
463
until this client is disabled
464
extended_timeout: extra long timeout when secret has been sent
273
465
runtime_expansions: Allowed attributes for runtime expansion.
466
expires: datetime.datetime(); time (UTC) when a client will be
468
server_settings: The server_settings dict from main()
276
471
runtime_expansions = ("approval_delay", "approval_duration",
277
"created", "enabled", "fingerprint",
278
"host", "interval", "last_checked_ok",
472
"created", "enabled", "expires",
473
"fingerprint", "host", "interval",
474
"last_approval_request", "last_checked_ok",
279
475
"last_enabled", "name", "timeout")
478
"extended_timeout": "PT15M",
480
"checker": "fping -q -- %%(host)s",
482
"approval_delay": "PT0S",
483
"approval_duration": "PT1S",
484
"approved_by_default": "True",
282
def _timedelta_to_milliseconds(td):
283
"Convert a datetime.timedelta() to milliseconds"
284
return ((td.days * 24 * 60 * 60 * 1000)
285
+ (td.seconds * 1000)
286
+ (td.microseconds // 1000))
288
def timeout_milliseconds(self):
289
"Return the 'timeout' attribute in milliseconds"
290
return self._timedelta_to_milliseconds(self.timeout)
292
def interval_milliseconds(self):
293
"Return the 'interval' attribute in milliseconds"
294
return self._timedelta_to_milliseconds(self.interval)
296
def approval_delay_milliseconds(self):
297
return self._timedelta_to_milliseconds(self.approval_delay)
299
def __init__(self, name = None, disable_hook=None, config=None):
300
"""Note: the 'checker' key in 'config' sets the
301
'checker_command' attribute and *not* the 'checker'
489
def config_parser(config):
490
"""Construct a new dict of client settings of this form:
491
{ client_name: {setting_name: value, ...}, ...}
492
with exceptions for any special settings as defined above.
493
NOTE: Must be a pure function. Must return the same result
494
value given the same arguments.
497
for client_name in config.sections():
498
section = dict(config.items(client_name))
499
client = settings[client_name] = {}
501
client["host"] = section["host"]
502
# Reformat values from string types to Python types
503
client["approved_by_default"] = config.getboolean(
504
client_name, "approved_by_default")
505
client["enabled"] = config.getboolean(client_name,
508
# Uppercase and remove spaces from fingerprint for later
509
# comparison purposes with return value from the
510
# fingerprint() function
511
client["fingerprint"] = (section["fingerprint"].upper()
513
if "secret" in section:
514
client["secret"] = section["secret"].decode("base64")
515
elif "secfile" in section:
516
with open(os.path.expanduser(os.path.expandvars
517
(section["secfile"])),
519
client["secret"] = secfile.read()
521
raise TypeError("No secret or secfile for section {}"
523
client["timeout"] = string_to_delta(section["timeout"])
524
client["extended_timeout"] = string_to_delta(
525
section["extended_timeout"])
526
client["interval"] = string_to_delta(section["interval"])
527
client["approval_delay"] = string_to_delta(
528
section["approval_delay"])
529
client["approval_duration"] = string_to_delta(
530
section["approval_duration"])
531
client["checker_command"] = section["checker"]
532
client["last_approval_request"] = None
533
client["last_checked_ok"] = None
534
client["last_checker_status"] = -2
538
def __init__(self, settings, name = None, server_settings=None):
540
if server_settings is None:
542
self.server_settings = server_settings
543
# adding all client settings
544
for setting, value in settings.items():
545
setattr(self, setting, value)
548
if not hasattr(self, "last_enabled"):
549
self.last_enabled = datetime.datetime.utcnow()
550
if not hasattr(self, "expires"):
551
self.expires = (datetime.datetime.utcnow()
554
self.last_enabled = None
306
557
logger.debug("Creating client %r", self.name)
307
# Uppercase and remove spaces from fingerprint for later
308
# comparison purposes with return value from the fingerprint()
310
self.fingerprint = (config["fingerprint"].upper()
312
558
logger.debug(" Fingerprint: %s", self.fingerprint)
313
if "secret" in config:
314
self.secret = config["secret"].decode("base64")
315
elif "secfile" in config:
316
with open(os.path.expanduser(os.path.expandvars
317
(config["secfile"])),
319
self.secret = secfile.read()
321
raise TypeError("No secret or secfile for client %s"
323
self.host = config.get("host", "")
324
self.created = datetime.datetime.utcnow()
326
self.last_approval_request = None
327
self.last_enabled = None
328
self.last_checked_ok = None
329
self.timeout = string_to_delta(config["timeout"])
330
self.interval = string_to_delta(config["interval"])
331
self.disable_hook = disable_hook
559
self.created = settings.get("created",
560
datetime.datetime.utcnow())
562
# attributes specific for this server instance
332
563
self.checker = None
333
564
self.checker_initiator_tag = None
334
565
self.disable_initiator_tag = None
335
566
self.checker_callback_tag = None
336
self.checker_command = config["checker"]
337
567
self.current_checker_command = None
338
self.last_connect = None
339
self._approved = None
340
self.approved_by_default = config.get("approved_by_default",
342
569
self.approvals_pending = 0
343
self.approval_delay = string_to_delta(
344
config["approval_delay"])
345
self.approval_duration = string_to_delta(
346
config["approval_duration"])
347
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
570
self.changedstate = multiprocessing_manager.Condition(
571
multiprocessing_manager.Lock())
572
self.client_structure = [attr
573
for attr in self.__dict__.iterkeys()
574
if not attr.startswith("_")]
575
self.client_structure.append("client_structure")
577
for name, t in inspect.getmembers(
578
type(self), lambda obj: isinstance(obj, property)):
579
if not name.startswith("_"):
580
self.client_structure.append(name)
582
# Send notice to process children that client state has changed
349
583
def send_changedstate(self):
350
self.changedstate.acquire()
351
self.changedstate.notify_all()
352
self.changedstate.release()
584
with self.changedstate:
585
self.changedstate.notify_all()
354
587
def enable(self):
355
588
"""Start this client's checker and timeout hooks"""
356
589
if getattr(self, "enabled", False):
357
590
# Already enabled
359
self.send_changedstate()
592
self.expires = datetime.datetime.utcnow() + self.timeout
360
594
self.last_enabled = datetime.datetime.utcnow()
361
# Schedule a new checker to be started an 'interval' from now,
362
# and every interval from then on.
363
self.checker_initiator_tag = (gobject.timeout_add
364
(self.interval_milliseconds(),
366
# Schedule a disable() when 'timeout' has passed
367
self.disable_initiator_tag = (gobject.timeout_add
368
(self.timeout_milliseconds(),
371
# Also start a new checker *right now*.
596
self.send_changedstate()
374
598
def disable(self, quiet=True):
375
599
"""Disable this client."""
376
600
if not getattr(self, "enabled", False):
379
self.send_changedstate()
381
603
logger.info("Disabling client %s", self.name)
382
if getattr(self, "disable_initiator_tag", False):
604
if getattr(self, "disable_initiator_tag", None) is not None:
383
605
gobject.source_remove(self.disable_initiator_tag)
384
606
self.disable_initiator_tag = None
385
if getattr(self, "checker_initiator_tag", False):
608
if getattr(self, "checker_initiator_tag", None) is not None:
386
609
gobject.source_remove(self.checker_initiator_tag)
387
610
self.checker_initiator_tag = None
388
611
self.stop_checker()
389
if self.disable_hook:
390
self.disable_hook(self)
391
612
self.enabled = False
614
self.send_changedstate()
392
615
# Do not run this again if called by a gobject.timeout_add
395
618
def __del__(self):
396
self.disable_hook = None
621
def init_checker(self):
622
# Schedule a new checker to be started an 'interval' from now,
623
# and every interval from then on.
624
if self.checker_initiator_tag is not None:
625
gobject.source_remove(self.checker_initiator_tag)
626
self.checker_initiator_tag = gobject.timeout_add(
627
int(self.interval.total_seconds() * 1000),
629
# Schedule a disable() when 'timeout' has passed
630
if self.disable_initiator_tag is not None:
631
gobject.source_remove(self.disable_initiator_tag)
632
self.disable_initiator_tag = gobject.timeout_add(
633
int(self.timeout.total_seconds() * 1000), self.disable)
634
# Also start a new checker *right now*.
399
637
def checker_callback(self, pid, condition, command):
400
638
"""The checker has completed, so take appropriate actions."""
401
639
self.checker_callback_tag = None
402
640
self.checker = None
403
641
if os.WIFEXITED(condition):
404
exitstatus = os.WEXITSTATUS(condition)
642
self.last_checker_status = os.WEXITSTATUS(condition)
643
if self.last_checker_status == 0:
406
644
logger.info("Checker for %(name)s succeeded",
408
646
self.checked_ok()
410
logger.info("Checker for %(name)s failed",
648
logger.info("Checker for %(name)s failed", vars(self))
650
self.last_checker_status = -1
413
651
logger.warning("Checker for %(name)s crashed?",
416
654
def checked_ok(self):
417
"""Bump up the timeout for this client.
419
This should only be called when the client has been seen,
655
"""Assert that the client has been seen, alive and well."""
422
656
self.last_checked_ok = datetime.datetime.utcnow()
423
gobject.source_remove(self.disable_initiator_tag)
424
self.disable_initiator_tag = (gobject.timeout_add
425
(self.timeout_milliseconds(),
657
self.last_checker_status = 0
660
def bump_timeout(self, timeout=None):
661
"""Bump up the timeout for this client."""
663
timeout = self.timeout
664
if self.disable_initiator_tag is not None:
665
gobject.source_remove(self.disable_initiator_tag)
666
self.disable_initiator_tag = None
667
if getattr(self, "enabled", False):
668
self.disable_initiator_tag = gobject.timeout_add(
669
int(timeout.total_seconds() * 1000), self.disable)
670
self.expires = datetime.datetime.utcnow() + timeout
428
672
def need_approval(self):
429
673
self.last_approval_request = datetime.datetime.utcnow()
457
702
self.current_checker_command)
458
703
# Start a new checker if needed
459
704
if self.checker is None:
705
# Escape attributes for the shell
707
attr: re.escape(str(getattr(self, attr)))
708
for attr in self.runtime_expansions }
461
# In case checker_command has exactly one % operator
462
command = self.checker_command % self.host
464
# Escape attributes for the shell
465
escaped_attrs = dict(
467
re.escape(unicode(str(getattr(self, attr, "")),
471
self.runtime_expansions)
474
command = self.checker_command % escaped_attrs
475
except TypeError, error:
476
logger.error('Could not format string "%s":'
477
' %s', self.checker_command, error)
478
return True # Try again later
710
command = self.checker_command % escaped_attrs
711
except TypeError as error:
712
logger.error('Could not format string "%s"',
713
self.checker_command,
715
return True # Try again later
479
716
self.current_checker_command = command
481
logger.info("Starting checker %r for %s",
718
logger.info("Starting checker %r for %s", command,
483
720
# We don't need to redirect stdout and stderr, since
484
721
# in normal mode, that is already done by daemon(),
485
722
# and in debug mode we don't want to. (Stdin is
486
723
# always replaced by /dev/null.)
724
# The exception is when not debugging but nevertheless
725
# running in the foreground; use the previously
728
if (not self.server_settings["debug"]
729
and self.server_settings["foreground"]):
730
popen_args.update({"stdout": wnull,
487
732
self.checker = subprocess.Popen(command,
490
self.checker_callback_tag = (gobject.child_watch_add
492
self.checker_callback,
494
# The checker may have completed before the gobject
495
# watch was added. Check for this.
737
except OSError as error:
738
logger.error("Failed to start subprocess",
741
self.checker_callback_tag = gobject.child_watch_add(
742
self.checker.pid, self.checker_callback, data=command)
743
# The checker may have completed before the gobject
744
# watch was added. Check for this.
496
746
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
498
gobject.source_remove(self.checker_callback_tag)
499
self.checker_callback(pid, status, command)
500
except OSError, error:
501
logger.error("Failed to start subprocess: %s",
747
except OSError as error:
748
if error.errno == errno.ECHILD:
749
# This should never happen
750
logger.error("Child process vanished",
755
gobject.source_remove(self.checker_callback_tag)
756
self.checker_callback(pid, status, command)
503
757
# Re-run this periodically if run by gobject.timeout_add
573
872
class DBusObjectWithProperties(dbus.service.Object):
574
873
"""A D-Bus object with properties.
576
875
Classes inheriting from this can use the dbus_service_property
577
876
decorator to expose methods as D-Bus properties. It exposes the
578
877
standard Get(), Set(), and GetAll() methods on the D-Bus.
582
def _is_dbus_property(obj):
583
return getattr(obj, "_dbus_is_property", False)
881
def _is_dbus_thing(thing):
882
"""Returns a function testing if an attribute is a D-Bus thing
884
If called like _is_dbus_thing("method") it returns a function
885
suitable for use as predicate to inspect.getmembers().
887
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
585
def _get_all_dbus_properties(self):
890
def _get_all_dbus_things(self, thing):
586
891
"""Returns a generator of (name, attribute) pairs
588
return ((prop._dbus_name, prop)
590
inspect.getmembers(self, self._is_dbus_property))
893
return ((getattr(athing.__get__(self), "_dbus_name", name),
894
athing.__get__(self))
895
for cls in self.__class__.__mro__
897
inspect.getmembers(cls, self._is_dbus_thing(thing)))
592
899
def _get_dbus_property(self, interface_name, property_name):
593
900
"""Returns a bound method if one exists which is a D-Bus
594
901
property with the specified name and interface.
596
for name in (property_name,
597
property_name + "_dbus_property"):
598
prop = getattr(self, name, None)
600
or not self._is_dbus_property(prop)
601
or prop._dbus_name != property_name
602
or (interface_name and prop._dbus_interface
603
and interface_name != prop._dbus_interface)):
903
for cls in self.__class__.__mro__:
904
for name, value in inspect.getmembers(
905
cls, self._is_dbus_thing("property")):
906
if (value._dbus_name == property_name
907
and value._dbus_interface == interface_name):
908
return value.__get__(self)
606
910
# No such property
607
raise DBusPropertyNotFound(self.dbus_object_path + ":"
608
+ interface_name + "."
911
raise DBusPropertyNotFound("{}:{}.{}".format(
912
self.dbus_object_path, interface_name, property_name))
611
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
914
@dbus.service.method(dbus.PROPERTIES_IFACE,
612
916
out_signature="v")
613
917
def Get(self, interface_name, property_name):
614
918
"""Standard D-Bus property Get() method, see D-Bus standard.
658
965
if not hasattr(value, "variant_level"):
966
properties[name] = value
661
all[name] = type(value)(value, variant_level=
662
value.variant_level+1)
663
return dbus.Dictionary(all, signature="sv")
968
properties[name] = type(value)(
969
value, variant_level = value.variant_level + 1)
970
return dbus.Dictionary(properties, signature="sv")
972
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
973
def PropertiesChanged(self, interface_name, changed_properties,
974
invalidated_properties):
975
"""Standard D-Bus PropertiesChanged() signal, see D-Bus
665
980
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
666
981
out_signature="s",
667
982
path_keyword='object_path',
668
983
connection_keyword='connection')
669
984
def Introspect(self, object_path, connection):
670
"""Standard D-Bus method, overloaded to insert property tags.
985
"""Overloading of standard D-Bus method.
987
Inserts property tags and interface annotation tags.
672
989
xmlstring = dbus.service.Object.Introspect(self, object_path,
675
992
document = xml.dom.minidom.parseString(xmlstring)
676
994
def make_tag(document, name, prop):
677
995
e = document.createElement("property")
678
996
e.setAttribute("name", name)
679
997
e.setAttribute("type", prop._dbus_signature)
680
998
e.setAttribute("access", prop._dbus_access)
682
1001
for if_tag in document.getElementsByTagName("interface"):
683
1003
for tag in (make_tag(document, name, prop)
685
in self._get_all_dbus_properties()
1005
in self._get_all_dbus_things("property")
686
1006
if prop._dbus_interface
687
1007
== if_tag.getAttribute("name")):
688
1008
if_tag.appendChild(tag)
1009
# Add annotation tags
1010
for typ in ("method", "signal", "property"):
1011
for tag in if_tag.getElementsByTagName(typ):
1013
for name, prop in (self.
1014
_get_all_dbus_things(typ)):
1015
if (name == tag.getAttribute("name")
1016
and prop._dbus_interface
1017
== if_tag.getAttribute("name")):
1018
annots.update(getattr(
1019
prop, "_dbus_annotations", {}))
1020
for name, value in annots.items():
1021
ann_tag = document.createElement(
1023
ann_tag.setAttribute("name", name)
1024
ann_tag.setAttribute("value", value)
1025
tag.appendChild(ann_tag)
1026
# Add interface annotation tags
1027
for annotation, value in dict(
1028
itertools.chain.from_iterable(
1029
annotations().items()
1030
for name, annotations
1031
in self._get_all_dbus_things("interface")
1032
if name == if_tag.getAttribute("name")
1034
ann_tag = document.createElement("annotation")
1035
ann_tag.setAttribute("name", annotation)
1036
ann_tag.setAttribute("value", value)
1037
if_tag.appendChild(ann_tag)
689
1038
# Add the names to the return values for the
690
1039
# "org.freedesktop.DBus.Properties" methods
691
1040
if (if_tag.getAttribute("name")
704
1053
xmlstring = document.toxml("utf-8")
705
1054
document.unlink()
706
1055
except (AttributeError, xml.dom.DOMException,
707
xml.parsers.expat.ExpatError), error:
1056
xml.parsers.expat.ExpatError) as error:
708
1057
logger.error("Failed to override Introspection method",
710
1059
return xmlstring
1062
def datetime_to_dbus(dt, variant_level=0):
1063
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1065
return dbus.String("", variant_level = variant_level)
1066
return dbus.String(dt.isoformat(), variant_level=variant_level)
1069
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1070
"""A class decorator; applied to a subclass of
1071
dbus.service.Object, it will add alternate D-Bus attributes with
1072
interface names according to the "alt_interface_names" mapping.
1075
@alternate_dbus_interfaces({"org.example.Interface":
1076
"net.example.AlternateInterface"})
1077
class SampleDBusObject(dbus.service.Object):
1078
@dbus.service.method("org.example.Interface")
1079
def SampleDBusMethod():
1082
The above "SampleDBusMethod" on "SampleDBusObject" will be
1083
reachable via two interfaces: "org.example.Interface" and
1084
"net.example.AlternateInterface", the latter of which will have
1085
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1086
"true", unless "deprecate" is passed with a False value.
1088
This works for methods and signals, and also for D-Bus properties
1089
(from DBusObjectWithProperties) and interfaces (from the
1090
dbus_interface_annotations decorator).
1094
for orig_interface_name, alt_interface_name in (
1095
alt_interface_names.items()):
1097
interface_names = set()
1098
# Go though all attributes of the class
1099
for attrname, attribute in inspect.getmembers(cls):
1100
# Ignore non-D-Bus attributes, and D-Bus attributes
1101
# with the wrong interface name
1102
if (not hasattr(attribute, "_dbus_interface")
1103
or not attribute._dbus_interface.startswith(
1104
orig_interface_name)):
1106
# Create an alternate D-Bus interface name based on
1108
alt_interface = attribute._dbus_interface.replace(
1109
orig_interface_name, alt_interface_name)
1110
interface_names.add(alt_interface)
1111
# Is this a D-Bus signal?
1112
if getattr(attribute, "_dbus_is_signal", False):
1113
# Extract the original non-method undecorated
1114
# function by black magic
1115
nonmethod_func = (dict(
1116
zip(attribute.func_code.co_freevars,
1117
attribute.__closure__))
1118
["func"].cell_contents)
1119
# Create a new, but exactly alike, function
1120
# object, and decorate it to be a new D-Bus signal
1121
# with the alternate D-Bus interface name
1122
new_function = (dbus.service.signal(
1123
alt_interface, attribute._dbus_signature)
1124
(types.FunctionType(
1125
nonmethod_func.func_code,
1126
nonmethod_func.func_globals,
1127
nonmethod_func.func_name,
1128
nonmethod_func.func_defaults,
1129
nonmethod_func.func_closure)))
1130
# Copy annotations, if any
1132
new_function._dbus_annotations = dict(
1133
attribute._dbus_annotations)
1134
except AttributeError:
1136
# Define a creator of a function to call both the
1137
# original and alternate functions, so both the
1138
# original and alternate signals gets sent when
1139
# the function is called
1140
def fixscope(func1, func2):
1141
"""This function is a scope container to pass
1142
func1 and func2 to the "call_both" function
1143
outside of its arguments"""
1145
def call_both(*args, **kwargs):
1146
"""This function will emit two D-Bus
1147
signals by calling func1 and func2"""
1148
func1(*args, **kwargs)
1149
func2(*args, **kwargs)
1152
# Create the "call_both" function and add it to
1154
attr[attrname] = fixscope(attribute, new_function)
1155
# Is this a D-Bus method?
1156
elif getattr(attribute, "_dbus_is_method", False):
1157
# Create a new, but exactly alike, function
1158
# object. Decorate it to be a new D-Bus method
1159
# with the alternate D-Bus interface name. Add it
1162
dbus.service.method(
1164
attribute._dbus_in_signature,
1165
attribute._dbus_out_signature)
1166
(types.FunctionType(attribute.func_code,
1167
attribute.func_globals,
1168
attribute.func_name,
1169
attribute.func_defaults,
1170
attribute.func_closure)))
1171
# Copy annotations, if any
1173
attr[attrname]._dbus_annotations = dict(
1174
attribute._dbus_annotations)
1175
except AttributeError:
1177
# Is this a D-Bus property?
1178
elif getattr(attribute, "_dbus_is_property", False):
1179
# Create a new, but exactly alike, function
1180
# object, and decorate it to be a new D-Bus
1181
# property with the alternate D-Bus interface
1182
# name. Add it to the class.
1183
attr[attrname] = (dbus_service_property(
1184
alt_interface, attribute._dbus_signature,
1185
attribute._dbus_access,
1186
attribute._dbus_get_args_options
1188
(types.FunctionType(
1189
attribute.func_code,
1190
attribute.func_globals,
1191
attribute.func_name,
1192
attribute.func_defaults,
1193
attribute.func_closure)))
1194
# Copy annotations, if any
1196
attr[attrname]._dbus_annotations = dict(
1197
attribute._dbus_annotations)
1198
except AttributeError:
1200
# Is this a D-Bus interface?
1201
elif getattr(attribute, "_dbus_is_interface", False):
1202
# Create a new, but exactly alike, function
1203
# object. Decorate it to be a new D-Bus interface
1204
# with the alternate D-Bus interface name. Add it
1207
dbus_interface_annotations(alt_interface)
1208
(types.FunctionType(attribute.func_code,
1209
attribute.func_globals,
1210
attribute.func_name,
1211
attribute.func_defaults,
1212
attribute.func_closure)))
1214
# Deprecate all alternate interfaces
1215
iname="_AlternateDBusNames_interface_annotation{}"
1216
for interface_name in interface_names:
1218
@dbus_interface_annotations(interface_name)
1220
return { "org.freedesktop.DBus.Deprecated":
1222
# Find an unused name
1223
for aname in (iname.format(i)
1224
for i in itertools.count()):
1225
if aname not in attr:
1229
# Replace the class with a new subclass of it with
1230
# methods, signals, etc. as created above.
1231
cls = type(b"{}Alternate".format(cls.__name__),
1238
@alternate_dbus_interfaces({"se.recompile.Mandos":
1239
"se.bsnet.fukt.Mandos"})
713
1240
class ClientDBus(Client, DBusObjectWithProperties):
714
1241
"""A Client class using D-Bus
721
1248
runtime_expansions = (Client.runtime_expansions
722
+ ("dbus_object_path",))
1249
+ ("dbus_object_path", ))
1251
_interface = "se.recompile.Mandos.Client"
724
1253
# dbus.service.Object doesn't use super(), so we can't either.
726
1255
def __init__(self, bus = None, *args, **kwargs):
727
self._approvals_pending = 0
729
1257
Client.__init__(self, *args, **kwargs)
730
1258
# Only now, when this client is initialized, can it show up on
732
client_object_name = unicode(self.name).translate(
1260
client_object_name = str(self.name).translate(
733
1261
{ord("."): ord("_"),
734
1262
ord("-"): ord("_")})
735
self.dbus_object_path = (dbus.ObjectPath
736
("/clients/" + client_object_name))
1263
self.dbus_object_path = dbus.ObjectPath(
1264
"/clients/" + client_object_name)
737
1265
DBusObjectWithProperties.__init__(self, self.bus,
738
1266
self.dbus_object_path)
740
def _get_approvals_pending(self):
741
return self._approvals_pending
742
def _set_approvals_pending(self, value):
743
old_value = self._approvals_pending
744
self._approvals_pending = value
746
if (hasattr(self, "dbus_object_path")
747
and bval is not bool(old_value)):
748
dbus_bool = dbus.Boolean(bval, variant_level=1)
749
self.PropertyChanged(dbus.String("ApprovalPending"),
752
approvals_pending = property(_get_approvals_pending,
753
_set_approvals_pending)
754
del _get_approvals_pending, _set_approvals_pending
757
def _datetime_to_dbus(dt, variant_level=0):
758
"""Convert a UTC datetime.datetime() to a D-Bus type."""
759
return dbus.String(dt.isoformat(),
760
variant_level=variant_level)
763
oldstate = getattr(self, "enabled", False)
764
r = Client.enable(self)
765
if oldstate != self.enabled:
767
self.PropertyChanged(dbus.String("Enabled"),
768
dbus.Boolean(True, variant_level=1))
769
self.PropertyChanged(
770
dbus.String("LastEnabled"),
771
self._datetime_to_dbus(self.last_enabled,
775
def disable(self, quiet = False):
776
oldstate = getattr(self, "enabled", False)
777
r = Client.disable(self, quiet=quiet)
778
if not quiet and oldstate != self.enabled:
780
self.PropertyChanged(dbus.String("Enabled"),
781
dbus.Boolean(False, variant_level=1))
1268
def notifychangeproperty(transform_func, dbus_name,
1269
type_func=lambda x: x,
1271
invalidate_only=False,
1272
_interface=_interface):
1273
""" Modify a variable so that it's a property which announces
1274
its changes to DBus.
1276
transform_fun: Function that takes a value and a variant_level
1277
and transforms it to a D-Bus type.
1278
dbus_name: D-Bus name of the variable
1279
type_func: Function that transform the value before sending it
1280
to the D-Bus. Default: no transform
1281
variant_level: D-Bus variant level. Default: 1
1283
attrname = "_{}".format(dbus_name)
1285
def setter(self, value):
1286
if hasattr(self, "dbus_object_path"):
1287
if (not hasattr(self, attrname) or
1288
type_func(getattr(self, attrname, None))
1289
!= type_func(value)):
1291
self.PropertiesChanged(
1292
_interface, dbus.Dictionary(),
1293
dbus.Array((dbus_name, )))
1295
dbus_value = transform_func(
1297
variant_level = variant_level)
1298
self.PropertyChanged(dbus.String(dbus_name),
1300
self.PropertiesChanged(
1302
dbus.Dictionary({ dbus.String(dbus_name):
1305
setattr(self, attrname, value)
1307
return property(lambda self: getattr(self, attrname), setter)
1309
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1310
approvals_pending = notifychangeproperty(dbus.Boolean,
1313
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1314
last_enabled = notifychangeproperty(datetime_to_dbus,
1316
checker = notifychangeproperty(
1317
dbus.Boolean, "CheckerRunning",
1318
type_func = lambda checker: checker is not None)
1319
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1321
last_checker_status = notifychangeproperty(dbus.Int16,
1322
"LastCheckerStatus")
1323
last_approval_request = notifychangeproperty(
1324
datetime_to_dbus, "LastApprovalRequest")
1325
approved_by_default = notifychangeproperty(dbus.Boolean,
1326
"ApprovedByDefault")
1327
approval_delay = notifychangeproperty(
1328
dbus.UInt64, "ApprovalDelay",
1329
type_func = lambda td: td.total_seconds() * 1000)
1330
approval_duration = notifychangeproperty(
1331
dbus.UInt64, "ApprovalDuration",
1332
type_func = lambda td: td.total_seconds() * 1000)
1333
host = notifychangeproperty(dbus.String, "Host")
1334
timeout = notifychangeproperty(
1335
dbus.UInt64, "Timeout",
1336
type_func = lambda td: td.total_seconds() * 1000)
1337
extended_timeout = notifychangeproperty(
1338
dbus.UInt64, "ExtendedTimeout",
1339
type_func = lambda td: td.total_seconds() * 1000)
1340
interval = notifychangeproperty(
1341
dbus.UInt64, "Interval",
1342
type_func = lambda td: td.total_seconds() * 1000)
1343
checker_command = notifychangeproperty(dbus.String, "Checker")
1344
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1345
invalidate_only=True)
1347
del notifychangeproperty
784
1349
def __del__(self, *args, **kwargs):
812
1374
return Client.checker_callback(self, pid, condition, command,
813
1375
*args, **kwargs)
815
def checked_ok(self, *args, **kwargs):
816
r = Client.checked_ok(self, *args, **kwargs)
818
self.PropertyChanged(
819
dbus.String("LastCheckedOK"),
820
(self._datetime_to_dbus(self.last_checked_ok,
824
def need_approval(self, *args, **kwargs):
825
r = Client.need_approval(self, *args, **kwargs)
827
self.PropertyChanged(
828
dbus.String("LastApprovalRequest"),
829
(self._datetime_to_dbus(self.last_approval_request,
833
1377
def start_checker(self, *args, **kwargs):
834
old_checker = self.checker
835
if self.checker is not None:
836
old_checker_pid = self.checker.pid
838
old_checker_pid = None
1378
old_checker_pid = getattr(self.checker, "pid", None)
839
1379
r = Client.start_checker(self, *args, **kwargs)
840
1380
# Only if new checker process was started
841
1381
if (self.checker is not None
842
1382
and old_checker_pid != self.checker.pid):
843
1383
# Emit D-Bus signal
844
1384
self.CheckerStarted(self.current_checker_command)
845
self.PropertyChanged(
846
dbus.String("CheckerRunning"),
847
dbus.Boolean(True, variant_level=1))
850
def stop_checker(self, *args, **kwargs):
851
old_checker = getattr(self, "checker", None)
852
r = Client.stop_checker(self, *args, **kwargs)
853
if (old_checker is not None
854
and getattr(self, "checker", None) is None):
855
self.PropertyChanged(dbus.String("CheckerRunning"),
856
dbus.Boolean(False, variant_level=1))
859
1387
def _reset_approved(self):
860
self._approved = None
1388
self.approved = None
863
1391
def approve(self, value=True):
1392
self.approved = value
1393
gobject.timeout_add(int(self.approval_duration.total_seconds()
1394
* 1000), self._reset_approved)
864
1395
self.send_changedstate()
865
self._approved = value
866
gobject.timeout_add(self._timedelta_to_milliseconds
867
(self.approval_duration),
868
self._reset_approved)
871
1397
## D-Bus methods, signals & properties
872
_interface = "se.bsnet.fukt.Mandos.Client"
1035
1555
# LastCheckedOK - property
1036
@dbus_service_property(_interface, signature="s",
1556
@dbus_service_property(_interface,
1037
1558
access="readwrite")
1038
1559
def LastCheckedOK_dbus_property(self, value=None):
1039
1560
if value is not None:
1040
1561
self.checked_ok()
1042
if self.last_checked_ok is None:
1043
return dbus.String("")
1044
return dbus.String(self._datetime_to_dbus(self
1563
return datetime_to_dbus(self.last_checked_ok)
1565
# LastCheckerStatus - property
1566
@dbus_service_property(_interface, signature="n", access="read")
1567
def LastCheckerStatus_dbus_property(self):
1568
return dbus.Int16(self.last_checker_status)
1570
# Expires - property
1571
@dbus_service_property(_interface, signature="s", access="read")
1572
def Expires_dbus_property(self):
1573
return datetime_to_dbus(self.expires)
1047
1575
# LastApprovalRequest - property
1048
1576
@dbus_service_property(_interface, signature="s", access="read")
1049
1577
def LastApprovalRequest_dbus_property(self):
1050
if self.last_approval_request is None:
1051
return dbus.String("")
1052
return dbus.String(self.
1053
_datetime_to_dbus(self
1054
.last_approval_request))
1578
return datetime_to_dbus(self.last_approval_request)
1056
1580
# Timeout - property
1057
@dbus_service_property(_interface, signature="t",
1581
@dbus_service_property(_interface,
1058
1583
access="readwrite")
1059
1584
def Timeout_dbus_property(self, value=None):
1060
1585
if value is None: # get
1061
return dbus.UInt64(self.timeout_milliseconds())
1586
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1587
old_timeout = self.timeout
1062
1588
self.timeout = datetime.timedelta(0, 0, 0, value)
1064
self.PropertyChanged(dbus.String("Timeout"),
1065
dbus.UInt64(value, variant_level=1))
1066
if getattr(self, "disable_initiator_tag", None) is None:
1068
# Reschedule timeout
1069
gobject.source_remove(self.disable_initiator_tag)
1070
self.disable_initiator_tag = None
1071
time_to_die = (self.
1072
_timedelta_to_milliseconds((self
1077
if time_to_die <= 0:
1078
# The timeout has passed
1081
self.disable_initiator_tag = (gobject.timeout_add
1082
(time_to_die, self.disable))
1589
# Reschedule disabling
1591
now = datetime.datetime.utcnow()
1592
self.expires += self.timeout - old_timeout
1593
if self.expires <= now:
1594
# The timeout has passed
1597
if (getattr(self, "disable_initiator_tag", None)
1600
gobject.source_remove(self.disable_initiator_tag)
1601
self.disable_initiator_tag = gobject.timeout_add(
1602
int((self.expires - now).total_seconds() * 1000),
1605
# ExtendedTimeout - property
1606
@dbus_service_property(_interface,
1609
def ExtendedTimeout_dbus_property(self, value=None):
1610
if value is None: # get
1611
return dbus.UInt64(self.extended_timeout.total_seconds()
1613
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1084
1615
# Interval - property
1085
@dbus_service_property(_interface, signature="t",
1616
@dbus_service_property(_interface,
1086
1618
access="readwrite")
1087
1619
def Interval_dbus_property(self, value=None):
1088
1620
if value is None: # get
1089
return dbus.UInt64(self.interval_milliseconds())
1621
return dbus.UInt64(self.interval.total_seconds() * 1000)
1090
1622
self.interval = datetime.timedelta(0, 0, 0, value)
1092
self.PropertyChanged(dbus.String("Interval"),
1093
dbus.UInt64(value, variant_level=1))
1094
1623
if getattr(self, "checker_initiator_tag", None) is None:
1096
# Reschedule checker run
1097
gobject.source_remove(self.checker_initiator_tag)
1098
self.checker_initiator_tag = (gobject.timeout_add
1099
(value, self.start_checker))
1100
self.start_checker() # Start one now, too
1626
# Reschedule checker run
1627
gobject.source_remove(self.checker_initiator_tag)
1628
self.checker_initiator_tag = gobject.timeout_add(
1629
value, self.start_checker)
1630
self.start_checker() # Start one now, too
1102
1632
# Checker - property
1103
@dbus_service_property(_interface, signature="s",
1633
@dbus_service_property(_interface,
1104
1635
access="readwrite")
1105
1636
def Checker_dbus_property(self, value=None):
1106
1637
if value is None: # get
1107
1638
return dbus.String(self.checker_command)
1108
self.checker_command = value
1110
self.PropertyChanged(dbus.String("Checker"),
1111
dbus.String(self.checker_command,
1639
self.checker_command = str(value)
1114
1641
# CheckerRunning - property
1115
@dbus_service_property(_interface, signature="b",
1642
@dbus_service_property(_interface,
1116
1644
access="readwrite")
1117
1645
def CheckerRunning_dbus_property(self, value=None):
1118
1646
if value is None: # get
1340
1871
def fingerprint(openpgp):
1341
1872
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1342
1873
# New GnuTLS "datum" with the OpenPGP public key
1343
datum = (gnutls.library.types
1344
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1347
ctypes.c_uint(len(openpgp))))
1874
datum = gnutls.library.types.gnutls_datum_t(
1875
ctypes.cast(ctypes.c_char_p(openpgp),
1876
ctypes.POINTER(ctypes.c_ubyte)),
1877
ctypes.c_uint(len(openpgp)))
1348
1878
# New empty GnuTLS certificate
1349
1879
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1350
(gnutls.library.functions
1351
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1880
gnutls.library.functions.gnutls_openpgp_crt_init(
1352
1882
# Import the OpenPGP public key into the certificate
1353
(gnutls.library.functions
1354
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1355
gnutls.library.constants
1356
.GNUTLS_OPENPGP_FMT_RAW))
1883
gnutls.library.functions.gnutls_openpgp_crt_import(
1884
crt, ctypes.byref(datum),
1885
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1357
1886
# Verify the self signature in the key
1358
1887
crtverify = ctypes.c_uint()
1359
(gnutls.library.functions
1360
.gnutls_openpgp_crt_verify_self(crt, 0,
1361
ctypes.byref(crtverify)))
1888
gnutls.library.functions.gnutls_openpgp_crt_verify_self(
1889
crt, 0, ctypes.byref(crtverify))
1362
1890
if crtverify.value != 0:
1363
1891
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1364
raise (gnutls.errors.CertificateSecurityError
1892
raise gnutls.errors.CertificateSecurityError(
1366
1894
# New buffer for the fingerprint
1367
1895
buf = ctypes.create_string_buffer(20)
1368
1896
buf_len = ctypes.c_size_t()
1369
1897
# Get the fingerprint from the certificate into the buffer
1370
(gnutls.library.functions
1371
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1372
ctypes.byref(buf_len)))
1898
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
1899
crt, ctypes.byref(buf), ctypes.byref(buf_len))
1373
1900
# Deinit the certificate
1374
1901
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1375
1902
# Convert the buffer to a Python bytestring
1376
1903
fpr = ctypes.string_at(buf, buf_len.value)
1377
1904
# Convert the bytestring to hexadecimal notation
1378
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1905
hex_fpr = binascii.hexlify(fpr).upper()
1382
1909
class MultiprocessingMixIn(object):
1383
1910
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1384
1912
def sub_process_main(self, request, address):
1386
1914
self.finish_request(request, address)
1388
1916
self.handle_error(request, address)
1389
1917
self.close_request(request)
1391
1919
def process_request(self, request, address):
1392
1920
"""Start a new process to process the request."""
1393
multiprocessing.Process(target = self.sub_process_main,
1394
args = (request, address)).start()
1921
proc = multiprocessing.Process(target = self.sub_process_main,
1922
args = (request, address))
1396
1927
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1397
1928
""" adds a pipe to the MixIn """
1398
1930
def process_request(self, request, client_address):
1399
1931
"""Overrides and wraps the original process_request().
1401
1933
This function creates a new pipe in self.pipe
1403
1935
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1405
super(MultiprocessingMixInWithPipe,
1406
self).process_request(request, client_address)
1937
proc = MultiprocessingMixIn.process_request(self, request,
1407
1939
self.child_pipe.close()
1408
self.add_pipe(parent_pipe)
1410
def add_pipe(self, parent_pipe):
1940
self.add_pipe(parent_pipe, proc)
1942
def add_pipe(self, parent_pipe, proc):
1411
1943
"""Dummy function; override as necessary"""
1412
raise NotImplementedError
1944
raise NotImplementedError()
1414
1947
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1415
1948
socketserver.TCPServer, object):
1485
2051
Assumes a gobject.MainLoop event loop.
1487
2054
def __init__(self, server_address, RequestHandlerClass,
1488
interface=None, use_ipv6=True, clients=None,
1489
gnutls_priority=None, use_dbus=True):
2058
gnutls_priority=None,
1490
2061
self.enabled = False
1491
2062
self.clients = clients
1492
2063
if self.clients is None:
1493
self.clients = set()
1494
2065
self.use_dbus = use_dbus
1495
2066
self.gnutls_priority = gnutls_priority
1496
2067
IPv6_TCPServer.__init__(self, server_address,
1497
2068
RequestHandlerClass,
1498
2069
interface = interface,
1499
use_ipv6 = use_ipv6)
2070
use_ipv6 = use_ipv6,
2071
socketfd = socketfd)
1500
2073
def server_activate(self):
1501
2074
if self.enabled:
1502
2075
return socketserver.TCPServer.server_activate(self)
1503
2077
def enable(self):
1504
2078
self.enabled = True
1505
def add_pipe(self, parent_pipe):
2080
def add_pipe(self, parent_pipe, proc):
1506
2081
# Call "handle_ipc" for both data and EOF events
1507
gobject.io_add_watch(parent_pipe.fileno(),
1508
gobject.IO_IN | gobject.IO_HUP,
1509
functools.partial(self.handle_ipc,
1510
parent_pipe = parent_pipe))
1512
def handle_ipc(self, source, condition, parent_pipe=None,
2082
gobject.io_add_watch(
2083
parent_pipe.fileno(),
2084
gobject.IO_IN | gobject.IO_HUP,
2085
functools.partial(self.handle_ipc,
2086
parent_pipe = parent_pipe,
2089
def handle_ipc(self, source, condition,
1513
2092
client_object=None):
1515
gobject.IO_IN: "IN", # There is data to read.
1516
gobject.IO_OUT: "OUT", # Data can be written (without
1518
gobject.IO_PRI: "PRI", # There is urgent data to read.
1519
gobject.IO_ERR: "ERR", # Error condition.
1520
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1521
# broken, usually for pipes and
1524
conditions_string = ' | '.join(name
1526
condition_names.iteritems()
1527
if cond & condition)
1528
# error or the other end of multiprocessing.Pipe has closed
1529
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
2093
# error, or the other end of multiprocessing.Pipe has closed
2094
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2095
# Wait for other process to exit
1532
2099
# Read a request from the child
1537
2104
fpr = request[1]
1538
2105
address = request[2]
1540
for c in self.clients:
2107
for c in self.clients.itervalues():
1541
2108
if c.fingerprint == fpr:
1545
logger.warning("Client not found for fingerprint: %s, ad"
1546
"dress: %s", fpr, address)
2112
logger.info("Client not found for fingerprint: %s, ad"
2113
"dress: %s", fpr, address)
1547
2114
if self.use_dbus:
1548
2115
# Emit D-Bus signal
1549
mandos_dbus_service.ClientNotFound(fpr, address[0])
2116
mandos_dbus_service.ClientNotFound(fpr,
1550
2118
parent_pipe.send(False)
1553
gobject.io_add_watch(parent_pipe.fileno(),
1554
gobject.IO_IN | gobject.IO_HUP,
1555
functools.partial(self.handle_ipc,
1556
parent_pipe = parent_pipe,
1557
client_object = client))
2121
gobject.io_add_watch(
2122
parent_pipe.fileno(),
2123
gobject.IO_IN | gobject.IO_HUP,
2124
functools.partial(self.handle_ipc,
2125
parent_pipe = parent_pipe,
2127
client_object = client))
1558
2128
parent_pipe.send(True)
1559
# remove the old hook in favor of the new above hook on same fileno
2129
# remove the old hook in favor of the new above hook on
1561
2132
if command == 'funcall':
1562
2133
funcname = request[1]
1563
2134
args = request[2]
1564
2135
kwargs = request[3]
1566
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
2137
parent_pipe.send(('data', getattr(client_object,
1568
2141
if command == 'getattr':
1569
2142
attrname = request[1]
1570
2143
if callable(client_object.__getattribute__(attrname)):
1571
parent_pipe.send(('function',))
2144
parent_pipe.send(('function', ))
1573
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
2147
'data', client_object.__getattribute__(attrname)))
1575
2149
if command == 'setattr':
1576
2150
attrname = request[1]
1577
2151
value = request[2]
1578
2152
setattr(client_object, attrname, value)
2157
def rfc3339_duration_to_delta(duration):
2158
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2160
>>> rfc3339_duration_to_delta("P7D")
2161
datetime.timedelta(7)
2162
>>> rfc3339_duration_to_delta("PT60S")
2163
datetime.timedelta(0, 60)
2164
>>> rfc3339_duration_to_delta("PT60M")
2165
datetime.timedelta(0, 3600)
2166
>>> rfc3339_duration_to_delta("PT24H")
2167
datetime.timedelta(1)
2168
>>> rfc3339_duration_to_delta("P1W")
2169
datetime.timedelta(7)
2170
>>> rfc3339_duration_to_delta("PT5M30S")
2171
datetime.timedelta(0, 330)
2172
>>> rfc3339_duration_to_delta("P1DT3M20S")
2173
datetime.timedelta(1, 200)
2176
# Parsing an RFC 3339 duration with regular expressions is not
2177
# possible - there would have to be multiple places for the same
2178
# values, like seconds. The current code, while more esoteric, is
2179
# cleaner without depending on a parsing library. If Python had a
2180
# built-in library for parsing we would use it, but we'd like to
2181
# avoid excessive use of external libraries.
2183
# New type for defining tokens, syntax, and semantics all-in-one
2184
Token = collections.namedtuple("Token",
2185
("regexp", # To match token; if
2186
# "value" is not None,
2187
# must have a "group"
2189
"value", # datetime.timedelta or
2191
"followers")) # Tokens valid after
2193
Token = collections.namedtuple("Token", (
2194
"regexp", # To match token; if "value" is not None, must have
2195
# a "group" containing digits
2196
"value", # datetime.timedelta or None
2197
"followers")) # Tokens valid after this token
2198
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2199
# the "duration" ABNF definition in RFC 3339, Appendix A.
2200
token_end = Token(re.compile(r"$"), None, frozenset())
2201
token_second = Token(re.compile(r"(\d+)S"),
2202
datetime.timedelta(seconds=1),
2203
frozenset((token_end, )))
2204
token_minute = Token(re.compile(r"(\d+)M"),
2205
datetime.timedelta(minutes=1),
2206
frozenset((token_second, token_end)))
2207
token_hour = Token(re.compile(r"(\d+)H"),
2208
datetime.timedelta(hours=1),
2209
frozenset((token_minute, token_end)))
2210
token_time = Token(re.compile(r"T"),
2212
frozenset((token_hour, token_minute,
2214
token_day = Token(re.compile(r"(\d+)D"),
2215
datetime.timedelta(days=1),
2216
frozenset((token_time, token_end)))
2217
token_month = Token(re.compile(r"(\d+)M"),
2218
datetime.timedelta(weeks=4),
2219
frozenset((token_day, token_end)))
2220
token_year = Token(re.compile(r"(\d+)Y"),
2221
datetime.timedelta(weeks=52),
2222
frozenset((token_month, token_end)))
2223
token_week = Token(re.compile(r"(\d+)W"),
2224
datetime.timedelta(weeks=1),
2225
frozenset((token_end, )))
2226
token_duration = Token(re.compile(r"P"), None,
2227
frozenset((token_year, token_month,
2228
token_day, token_time,
2230
# Define starting values
2231
value = datetime.timedelta() # Value so far
2233
followers = frozenset((token_duration,)) # Following valid tokens
2234
s = duration # String left to parse
2235
# Loop until end token is found
2236
while found_token is not token_end:
2237
# Search for any currently valid tokens
2238
for token in followers:
2239
match = token.regexp.match(s)
2240
if match is not None:
2242
if token.value is not None:
2243
# Value found, parse digits
2244
factor = int(match.group(1), 10)
2245
# Add to value so far
2246
value += factor * token.value
2247
# Strip token from string
2248
s = token.regexp.sub("", s, 1)
2251
# Set valid next tokens
2252
followers = found_token.followers
2255
# No currently valid tokens were found
2256
raise ValueError("Invalid RFC 3339 duration")
1583
2261
def string_to_delta(interval):
1584
2262
"""Parse a string and return a datetime.timedelta
1717
2392
"debug": "False",
1719
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2394
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2395
":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
1720
2396
"servicename": "Mandos",
1721
2397
"use_dbus": "True",
1722
2398
"use_ipv6": "True",
1723
2399
"debuglevel": "",
2402
"statedir": "/var/lib/mandos",
2403
"foreground": "False",
1726
2407
# Parse config file for server-global settings
1727
2408
server_config = configparser.SafeConfigParser(server_defaults)
1728
2409
del server_defaults
1729
server_config.read(os.path.join(options.configdir,
2410
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1731
2411
# Convert the SafeConfigParser object to a dict
1732
2412
server_settings = server_config.defaults()
1733
2413
# Use the appropriate methods on the non-string config options
1734
for option in ("debug", "use_dbus", "use_ipv6"):
2414
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
1735
2415
server_settings[option] = server_config.getboolean("DEFAULT",
1737
2417
if server_settings["port"]:
1738
2418
server_settings["port"] = server_config.getint("DEFAULT",
2420
if server_settings["socket"]:
2421
server_settings["socket"] = server_config.getint("DEFAULT",
2423
# Later, stdin will, and stdout and stderr might, be dup'ed
2424
# over with an opened os.devnull. But we don't want this to
2425
# happen with a supplied network socket.
2426
if 0 <= server_settings["socket"] <= 2:
2427
server_settings["socket"] = os.dup(server_settings
1740
2429
del server_config
1742
2431
# Override the settings from the config file with command line
1743
2432
# options, if set.
1744
2433
for option in ("interface", "address", "port", "debug",
1745
"priority", "servicename", "configdir",
1746
"use_dbus", "use_ipv6", "debuglevel"):
2434
"priority", "servicename", "configdir", "use_dbus",
2435
"use_ipv6", "debuglevel", "restore", "statedir",
2436
"socket", "foreground", "zeroconf"):
1747
2437
value = getattr(options, option)
1748
2438
if value is not None:
1749
2439
server_settings[option] = value
1751
2441
# Force all strings to be unicode
1752
2442
for option in server_settings.keys():
1753
if type(server_settings[option]) is str:
1754
server_settings[option] = unicode(server_settings[option])
2443
if isinstance(server_settings[option], bytes):
2444
server_settings[option] = (server_settings[option]
2446
# Force all boolean options to be boolean
2447
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2448
"foreground", "zeroconf"):
2449
server_settings[option] = bool(server_settings[option])
2450
# Debug implies foreground
2451
if server_settings["debug"]:
2452
server_settings["foreground"] = True
1755
2453
# Now we have our good server settings in "server_settings"
1757
2455
##################################################################
2457
if (not server_settings["zeroconf"]
2458
and not (server_settings["port"]
2459
or server_settings["socket"] != "")):
2460
parser.error("Needs port or socket to work without Zeroconf")
1759
2462
# For convenience
1760
2463
debug = server_settings["debug"]
1761
2464
debuglevel = server_settings["debuglevel"]
1762
2465
use_dbus = server_settings["use_dbus"]
1763
2466
use_ipv6 = server_settings["use_ipv6"]
2467
stored_state_path = os.path.join(server_settings["statedir"],
2469
foreground = server_settings["foreground"]
2470
zeroconf = server_settings["zeroconf"]
2473
initlogger(debug, logging.DEBUG)
2478
level = getattr(logging, debuglevel.upper())
2479
initlogger(debug, level)
1765
2481
if server_settings["servicename"] != "Mandos":
1766
syslogger.setFormatter(logging.Formatter
1767
('Mandos (%s) [%%(process)d]:'
1768
' %%(levelname)s: %%(message)s'
1769
% server_settings["servicename"]))
2482
syslogger.setFormatter(
2483
logging.Formatter('Mandos ({}) [%(process)d]:'
2484
' %(levelname)s: %(message)s'.format(
2485
server_settings["servicename"])))
1771
2487
# Parse config file with clients
1772
client_defaults = { "timeout": "1h",
1774
"checker": "fping -q -- %%(host)s",
1776
"approval_delay": "0s",
1777
"approval_duration": "1s",
1779
client_config = configparser.SafeConfigParser(client_defaults)
2488
client_config = configparser.SafeConfigParser(Client
1780
2490
client_config.read(os.path.join(server_settings["configdir"],
1781
2491
"clients.conf"))
1783
2493
global mandos_dbus_service
1784
2494
mandos_dbus_service = None
1786
tcp_server = MandosServer((server_settings["address"],
1787
server_settings["port"]),
1789
interface=(server_settings["interface"]
1793
server_settings["priority"],
1796
pidfilename = "/var/run/mandos.pid"
2497
if server_settings["socket"] != "":
2498
socketfd = server_settings["socket"]
2499
tcp_server = MandosServer(
2500
(server_settings["address"], server_settings["port"]),
2502
interface=(server_settings["interface"] or None),
2504
gnutls_priority=server_settings["priority"],
2508
pidfilename = "/run/mandos.pid"
2509
if not os.path.isdir("/run/."):
2510
pidfilename = "/var/run/mandos.pid"
1798
2513
pidfile = open(pidfilename, "w")
1800
logger.error("Could not open file %r", pidfilename)
2514
except IOError as e:
2515
logger.error("Could not open file %r", pidfilename,
1803
uid = pwd.getpwnam("_mandos").pw_uid
1804
gid = pwd.getpwnam("_mandos").pw_gid
2518
for name in ("_mandos", "mandos", "nobody"):
1807
uid = pwd.getpwnam("mandos").pw_uid
1808
gid = pwd.getpwnam("mandos").pw_gid
2520
uid = pwd.getpwnam(name).pw_uid
2521
gid = pwd.getpwnam(name).pw_gid
1809
2523
except KeyError:
1811
uid = pwd.getpwnam("nobody").pw_uid
1812
gid = pwd.getpwnam("nobody").pw_gid
1819
except OSError, error:
1820
if error[0] != errno.EPERM:
2531
except OSError as error:
2532
if error.errno != errno.EPERM:
1823
if not debug and not debuglevel:
1824
syslogger.setLevel(logging.WARNING)
1825
console.setLevel(logging.WARNING)
1827
level = getattr(logging, debuglevel.upper())
1828
syslogger.setLevel(level)
1829
console.setLevel(level)
1832
2536
# Enable all possible GnuTLS debugging
1839
2543
def debug_gnutls(level, string):
1840
2544
logger.debug("GnuTLS: %s", string[:-1])
1842
(gnutls.library.functions
1843
.gnutls_global_set_log_function(debug_gnutls))
2546
gnutls.library.functions.gnutls_global_set_log_function(
1845
2549
# Redirect stdin so all checkers get /dev/null
1846
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2550
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1847
2551
os.dup2(null, sys.stdin.fileno())
1851
# No console logging
1852
logger.removeHandler(console)
1854
2555
# Need to fork before connecting to D-Bus
1856
2557
# Close all input and output, do double fork, etc.
2560
# multiprocessing will use threads, so before we use gobject we
2561
# need to inform gobject that threads will be used.
2562
gobject.threads_init()
1859
2564
global main_loop
1860
2565
# From the Avahi example code
1861
DBusGMainLoop(set_as_default=True )
2566
DBusGMainLoop(set_as_default=True)
1862
2567
main_loop = gobject.MainLoop()
1863
2568
bus = dbus.SystemBus()
1864
2569
# End of Avahi example code
1867
bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1868
bus, do_not_queue=True)
1869
except dbus.exceptions.NameExistsException, e:
1870
logger.error(unicode(e) + ", disabling D-Bus")
2572
bus_name = dbus.service.BusName("se.recompile.Mandos",
2575
old_bus_name = dbus.service.BusName(
2576
"se.bsnet.fukt.Mandos", bus,
2578
except dbus.exceptions.DBusException as e:
2579
logger.error("Disabling D-Bus:", exc_info=e)
1871
2580
use_dbus = False
1872
2581
server_settings["use_dbus"] = False
1873
2582
tcp_server.use_dbus = False
1874
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1875
service = AvahiService(name = server_settings["servicename"],
1876
servicetype = "_mandos._tcp",
1877
protocol = protocol, bus = bus)
1878
if server_settings["interface"]:
1879
service.interface = (if_nametoindex
1880
(str(server_settings["interface"])))
2584
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2585
service = AvahiServiceToSyslog(
2586
name = server_settings["servicename"],
2587
servicetype = "_mandos._tcp",
2588
protocol = protocol,
2590
if server_settings["interface"]:
2591
service.interface = if_nametoindex(
2592
server_settings["interface"].encode("utf-8"))
1882
2594
global multiprocessing_manager
1883
2595
multiprocessing_manager = multiprocessing.Manager()
1885
2597
client_class = Client
1887
2599
client_class = functools.partial(ClientDBus, bus = bus)
1888
def client_config_items(config, section):
1889
special_settings = {
1890
"approved_by_default":
1891
lambda: config.getboolean(section,
1892
"approved_by_default"),
1894
for name, value in config.items(section):
2601
client_settings = Client.config_parser(client_config)
2602
old_client_settings = {}
2605
# This is used to redirect stdout and stderr for checker processes
2607
wnull = open(os.devnull, "w") # A writable /dev/null
2608
# Only used if server is running in foreground but not in debug
2610
if debug or not foreground:
2613
# Get client data and settings from last running state.
2614
if server_settings["restore"]:
2616
with open(stored_state_path, "rb") as stored_state:
2617
clients_data, old_client_settings = pickle.load(
2619
os.remove(stored_state_path)
2620
except IOError as e:
2621
if e.errno == errno.ENOENT:
2622
logger.warning("Could not load persistent state:"
2623
" {}".format(os.strerror(e.errno)))
2625
logger.critical("Could not load persistent state:",
2628
except EOFError as e:
2629
logger.warning("Could not load persistent state: "
2633
with PGPEngine() as pgp:
2634
for client_name, client in clients_data.items():
2635
# Skip removed clients
2636
if client_name not in client_settings:
2639
# Decide which value to use after restoring saved state.
2640
# We have three different values: Old config file,
2641
# new config file, and saved state.
2642
# New config value takes precedence if it differs from old
2643
# config value, otherwise use saved state.
2644
for name, value in client_settings[client_name].items():
2646
# For each value in new config, check if it
2647
# differs from the old config value (Except for
2648
# the "secret" attribute)
2649
if (name != "secret"
2651
old_client_settings[client_name][name])):
2652
client[name] = value
2656
# Clients who has passed its expire date can still be
2657
# enabled if its last checker was successful. Clients
2658
# whose checker succeeded before we stored its state is
2659
# assumed to have successfully run all checkers during
2661
if client["enabled"]:
2662
if datetime.datetime.utcnow() >= client["expires"]:
2663
if not client["last_checked_ok"]:
2665
"disabling client {} - Client never "
2666
"performed a successful checker".format(
2668
client["enabled"] = False
2669
elif client["last_checker_status"] != 0:
2671
"disabling client {} - Client last"
2672
" checker failed with error code"
2675
client["last_checker_status"]))
2676
client["enabled"] = False
2678
client["expires"] = (
2679
datetime.datetime.utcnow()
2680
+ client["timeout"])
2681
logger.debug("Last checker succeeded,"
2682
" keeping {} enabled".format(
1896
yield (name, special_settings[name]())
1900
tcp_server.clients.update(set(
1901
client_class(name = section,
1902
config= dict(client_config_items(
1903
client_config, section)))
1904
for section in client_config.sections()))
2685
client["secret"] = pgp.decrypt(
2686
client["encrypted_secret"],
2687
client_settings[client_name]["secret"])
2689
# If decryption fails, we use secret from new settings
2690
logger.debug("Failed to decrypt {} old secret".format(
2692
client["secret"] = (client_settings[client_name]
2695
# Add/remove clients based on new changes made to config
2696
for client_name in (set(old_client_settings)
2697
- set(client_settings)):
2698
del clients_data[client_name]
2699
for client_name in (set(client_settings)
2700
- set(old_client_settings)):
2701
clients_data[client_name] = client_settings[client_name]
2703
# Create all client objects
2704
for client_name, client in clients_data.items():
2705
tcp_server.clients[client_name] = client_class(
2708
server_settings = server_settings)
1905
2710
if not tcp_server.clients:
1906
2711
logger.warning("No clients defined")
1912
pidfile.write(str(pid) + "\n".encode("utf-8"))
1915
logger.error("Could not write to file %r with PID %d",
1918
# "pidfile" was never created
2714
if pidfile is not None:
2718
pidfile.write("{}\n".format(pid).encode("utf-8"))
2720
logger.error("Could not write to file %r with PID %d",
1920
2723
del pidfilename
1922
signal.signal(signal.SIGINT, signal.SIG_IGN)
1924
2725
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1925
2726
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1928
class MandosDBusService(dbus.service.Object):
2730
@alternate_dbus_interfaces(
2731
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2732
class MandosDBusService(DBusObjectWithProperties):
1929
2733
"""A D-Bus proxy object"""
1930
2735
def __init__(self):
1931
2736
dbus.service.Object.__init__(self, bus, "/")
1932
_interface = "se.bsnet.fukt.Mandos"
2738
_interface = "se.recompile.Mandos"
2740
@dbus_interface_annotations(_interface)
2743
"org.freedesktop.DBus.Property.EmitsChangedSignal":
1934
2746
@dbus.service.signal(_interface, signature="o")
1935
2747
def ClientAdded(self, objpath):
1983
2795
"Cleanup function; run on exit"
2799
multiprocessing.active_children()
2801
if not (tcp_server.clients or client_settings):
2804
# Store client before exiting. Secrets are encrypted with key
2805
# based on what config file has. If config file is
2806
# removed/edited, old secret will thus be unrecovable.
2808
with PGPEngine() as pgp:
2809
for client in tcp_server.clients.itervalues():
2810
key = client_settings[client.name]["secret"]
2811
client.encrypted_secret = pgp.encrypt(client.secret,
2815
# A list of attributes that can not be pickled
2817
exclude = { "bus", "changedstate", "secret",
2818
"checker", "server_settings" }
2819
for name, typ in inspect.getmembers(dbus.service
2823
client_dict["encrypted_secret"] = (client
2825
for attr in client.client_structure:
2826
if attr not in exclude:
2827
client_dict[attr] = getattr(client, attr)
2829
clients[client.name] = client_dict
2830
del client_settings[client.name]["secret"]
2833
with tempfile.NamedTemporaryFile(
2837
dir=os.path.dirname(stored_state_path),
2838
delete=False) as stored_state:
2839
pickle.dump((clients, client_settings), stored_state)
2840
tempname = stored_state.name
2841
os.rename(tempname, stored_state_path)
2842
except (IOError, OSError) as e:
2848
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2849
logger.warning("Could not save persistent state: {}"
2850
.format(os.strerror(e.errno)))
2852
logger.warning("Could not save persistent state:",
2856
# Delete all clients, and settings from config
1986
2857
while tcp_server.clients:
1987
client = tcp_server.clients.pop()
2858
name, client = tcp_server.clients.popitem()
1989
2860
client.remove_from_connection()
1990
client.disable_hook = None
1991
2861
# Don't signal anything except ClientRemoved
1992
2862
client.disable(quiet=True)
1994
2864
# Emit D-Bus signal
1995
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2865
mandos_dbus_service.ClientRemoved(
2866
client.dbus_object_path, client.name)
2867
client_settings.clear()
1998
2869
atexit.register(cleanup)
2000
for client in tcp_server.clients:
2871
for client in tcp_server.clients.itervalues():
2002
2873
# Emit D-Bus signal
2003
2874
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2875
# Need to initiate checking of clients
2877
client.init_checker()
2006
2879
tcp_server.enable()
2007
2880
tcp_server.server_activate()
2009
2882
# Find out what port we got
2010
service.port = tcp_server.socket.getsockname()[1]
2884
service.port = tcp_server.socket.getsockname()[1]
2012
2886
logger.info("Now listening on address %r, port %d,"
2013
" flowinfo %d, scope_id %d"
2014
% tcp_server.socket.getsockname())
2887
" flowinfo %d, scope_id %d",
2888
*tcp_server.socket.getsockname())
2016
logger.info("Now listening on address %r, port %d"
2017
% tcp_server.socket.getsockname())
2890
logger.info("Now listening on address %r, port %d",
2891
*tcp_server.socket.getsockname())
2019
2893
#service.interface = tcp_server.socket.getsockname()[3]
2022
# From the Avahi example code
2025
except dbus.exceptions.DBusException, error:
2026
logger.critical("DBusException: %s", error)
2029
# End of Avahi example code
2897
# From the Avahi example code
2900
except dbus.exceptions.DBusException as error:
2901
logger.critical("D-Bus Exception", exc_info=error)
2904
# End of Avahi example code
2031
2906
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2032
2907
lambda *args, **kwargs: