80
88
except ImportError:
81
89
SO_BINDTODEVICE = None
86
#logger = logging.getLogger(u'mandos')
87
logger = logging.Logger(u'mandos')
88
syslogger = (logging.handlers.SysLogHandler
89
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
90
address = "/dev/log"))
91
syslogger.setFormatter(logging.Formatter
92
(u'Mandos [%(process)d]: %(levelname)s:'
94
logger.addHandler(syslogger)
96
console = logging.StreamHandler()
97
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
100
logger.addHandler(console)
92
stored_state_file = "clients.pickle"
94
logger = logging.getLogger()
98
if_nametoindex = (ctypes.cdll.LoadLibrary
99
(ctypes.util.find_library("c"))
101
except (OSError, AttributeError):
102
def if_nametoindex(interface):
103
"Get an interface index the hard way, i.e. using fcntl()"
104
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
105
with contextlib.closing(socket.socket()) as s:
106
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
107
struct.pack(str("16s16x"),
109
interface_index = struct.unpack(str("I"),
111
return interface_index
114
def initlogger(debug, level=logging.WARNING):
115
"""init logger and add loglevel"""
118
syslogger = (logging.handlers.SysLogHandler
120
logging.handlers.SysLogHandler.LOG_DAEMON,
121
address = str("/dev/log")))
122
syslogger.setFormatter(logging.Formatter
123
('Mandos [%(process)d]: %(levelname)s:'
125
logger.addHandler(syslogger)
128
console = logging.StreamHandler()
129
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
133
logger.addHandler(console)
134
logger.setLevel(level)
137
class PGPError(Exception):
138
"""Exception if encryption/decryption fails"""
142
class PGPEngine(object):
143
"""A simple class for OpenPGP symmetric encryption & decryption"""
145
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
146
self.gnupgargs = ['--batch',
147
'--home', self.tempdir,
155
def __exit__(self, exc_type, exc_value, traceback):
163
if self.tempdir is not None:
164
# Delete contents of tempdir
165
for root, dirs, files in os.walk(self.tempdir,
167
for filename in files:
168
os.remove(os.path.join(root, filename))
170
os.rmdir(os.path.join(root, dirname))
172
os.rmdir(self.tempdir)
175
def password_encode(self, password):
176
# Passphrase can not be empty and can not contain newlines or
177
# NUL bytes. So we prefix it and hex encode it.
178
encoded = b"mandos" + binascii.hexlify(password)
179
if len(encoded) > 2048:
180
# GnuPG can't handle long passwords, so encode differently
181
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
182
.replace(b"\n", b"\\n")
183
.replace(b"\0", b"\\x00"))
186
def encrypt(self, data, password):
187
passphrase = self.password_encode(password)
188
with tempfile.NamedTemporaryFile(dir=self.tempdir
190
passfile.write(passphrase)
192
proc = subprocess.Popen(['gpg', '--symmetric',
196
stdin = subprocess.PIPE,
197
stdout = subprocess.PIPE,
198
stderr = subprocess.PIPE)
199
ciphertext, err = proc.communicate(input = data)
200
if proc.returncode != 0:
204
def decrypt(self, data, password):
205
passphrase = self.password_encode(password)
206
with tempfile.NamedTemporaryFile(dir = self.tempdir
208
passfile.write(passphrase)
210
proc = subprocess.Popen(['gpg', '--decrypt',
214
stdin = subprocess.PIPE,
215
stdout = subprocess.PIPE,
216
stderr = subprocess.PIPE)
217
decrypted_plaintext, err = proc.communicate(input
219
if proc.returncode != 0:
221
return decrypted_plaintext
102
224
class AvahiError(Exception):
103
225
def __init__(self, value, *args, **kwargs):
197
325
dbus.UInt16(self.port),
198
326
avahi.string_array_to_txt_array(self.TXT))
199
327
self.group.Commit()
200
329
def entry_group_state_changed(self, state, error):
201
330
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
331
logger.debug("Avahi entry group state change: %i", state)
204
333
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
334
logger.debug("Zeroconf service established.")
206
335
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.info(u"Zeroconf service name collision.")
336
logger.info("Zeroconf service name collision.")
209
338
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
339
logger.critical("Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
341
raise AvahiGroupError("State changed: {!s}"
214
344
def cleanup(self):
215
345
"""Derived from the Avahi example code"""
216
346
if self.group is not None:
349
except (dbus.exceptions.UnknownMethodException,
350
dbus.exceptions.DBusException):
218
352
self.group = None
219
def server_state_changed(self, state):
355
def server_state_changed(self, state, error=None):
220
356
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
357
logger.debug("Avahi server state change: %i", state)
358
bad_states = { avahi.SERVER_INVALID:
359
"Zeroconf server invalid",
360
avahi.SERVER_REGISTERING: None,
361
avahi.SERVER_COLLISION:
362
"Zeroconf server name collision",
363
avahi.SERVER_FAILURE:
364
"Zeroconf server failure" }
365
if state in bad_states:
366
if bad_states[state] is not None:
368
logger.error(bad_states[state])
370
logger.error(bad_states[state] + ": %r", error)
225
372
elif state == avahi.SERVER_RUNNING:
376
logger.debug("Unknown state: %r", state)
378
logger.debug("Unknown state: %r: %r", state, error)
227
380
def activate(self):
228
381
"""Derived from the Avahi example code"""
229
382
if self.server is None:
230
383
self.server = dbus.Interface(
231
384
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
385
avahi.DBUS_PATH_SERVER,
386
follow_name_owner_changes=True),
233
387
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
388
self.server.connect_to_signal("StateChanged",
235
389
self.server_state_changed)
236
390
self.server_state_changed(self.server.GetState())
393
class AvahiServiceToSyslog(AvahiService):
395
"""Add the new name to the syslog messages"""
396
ret = AvahiService.rename(self)
397
syslogger.setFormatter(logging.Formatter
398
('Mandos ({}) [%(process)d]:'
399
' %(levelname)s: %(message)s'
239
404
class Client(object):
240
405
"""A representation of a client host served by this server.
243
_approved: bool(); 'None' if not yet approved/disapproved
408
approved: bool(); 'None' if not yet approved/disapproved
244
409
approval_delay: datetime.timedelta(); Time to wait for approval
245
410
approval_duration: datetime.timedelta(); Duration of one approval
246
411
checker: subprocess.Popen(); a running checker process used
263
429
interval: datetime.timedelta(); How often to start a new checker
264
430
last_approval_request: datetime.datetime(); (UTC) or None
265
431
last_checked_ok: datetime.datetime(); (UTC) or None
266
last_enabled: datetime.datetime(); (UTC)
432
last_checker_status: integer between 0 and 255 reflecting exit
433
status of last checker. -1 reflects crashed
434
checker, -2 means no checker completed yet.
435
last_enabled: datetime.datetime(); (UTC) or None
267
436
name: string; from the config file, used in log messages and
268
437
D-Bus identifiers
269
438
secret: bytestring; sent verbatim (over TLS) to client
270
439
timeout: datetime.timedelta(); How long from last_checked_ok
271
440
until this client is disabled
441
extended_timeout: extra long timeout when secret has been sent
272
442
runtime_expansions: Allowed attributes for runtime expansion.
443
expires: datetime.datetime(); time (UTC) when a client will be
445
server_settings: The server_settings dict from main()
275
runtime_expansions = (u"approval_delay", u"approval_duration",
276
u"created", u"enabled", u"fingerprint",
277
u"host", u"interval", u"last_checked_ok",
278
u"last_enabled", u"name", u"timeout")
448
runtime_expansions = ("approval_delay", "approval_duration",
449
"created", "enabled", "expires",
450
"fingerprint", "host", "interval",
451
"last_approval_request", "last_checked_ok",
452
"last_enabled", "name", "timeout")
453
client_defaults = { "timeout": "PT5M",
454
"extended_timeout": "PT15M",
456
"checker": "fping -q -- %%(host)s",
458
"approval_delay": "PT0S",
459
"approval_duration": "PT1S",
460
"approved_by_default": "True",
281
def _timedelta_to_milliseconds(td):
282
"Convert a datetime.timedelta() to milliseconds"
283
return ((td.days * 24 * 60 * 60 * 1000)
284
+ (td.seconds * 1000)
285
+ (td.microseconds // 1000))
287
def timeout_milliseconds(self):
288
"Return the 'timeout' attribute in milliseconds"
289
return self._timedelta_to_milliseconds(self.timeout)
291
def interval_milliseconds(self):
292
"Return the 'interval' attribute in milliseconds"
293
return self._timedelta_to_milliseconds(self.interval)
295
def approval_delay_milliseconds(self):
296
return self._timedelta_to_milliseconds(self.approval_delay)
298
def __init__(self, name = None, disable_hook=None, config=None):
299
"""Note: the 'checker' key in 'config' sets the
300
'checker_command' attribute and *not* the 'checker'
465
def config_parser(config):
466
"""Construct a new dict of client settings of this form:
467
{ client_name: {setting_name: value, ...}, ...}
468
with exceptions for any special settings as defined above.
469
NOTE: Must be a pure function. Must return the same result
470
value given the same arguments.
473
for client_name in config.sections():
474
section = dict(config.items(client_name))
475
client = settings[client_name] = {}
477
client["host"] = section["host"]
478
# Reformat values from string types to Python types
479
client["approved_by_default"] = config.getboolean(
480
client_name, "approved_by_default")
481
client["enabled"] = config.getboolean(client_name,
484
client["fingerprint"] = (section["fingerprint"].upper()
486
if "secret" in section:
487
client["secret"] = section["secret"].decode("base64")
488
elif "secfile" in section:
489
with open(os.path.expanduser(os.path.expandvars
490
(section["secfile"])),
492
client["secret"] = secfile.read()
494
raise TypeError("No secret or secfile for section {}"
496
client["timeout"] = string_to_delta(section["timeout"])
497
client["extended_timeout"] = string_to_delta(
498
section["extended_timeout"])
499
client["interval"] = string_to_delta(section["interval"])
500
client["approval_delay"] = string_to_delta(
501
section["approval_delay"])
502
client["approval_duration"] = string_to_delta(
503
section["approval_duration"])
504
client["checker_command"] = section["checker"]
505
client["last_approval_request"] = None
506
client["last_checked_ok"] = None
507
client["last_checker_status"] = -2
511
def __init__(self, settings, name = None, server_settings=None):
305
logger.debug(u"Creating client %r", self.name)
513
if server_settings is None:
515
self.server_settings = server_settings
516
# adding all client settings
517
for setting, value in settings.items():
518
setattr(self, setting, value)
521
if not hasattr(self, "last_enabled"):
522
self.last_enabled = datetime.datetime.utcnow()
523
if not hasattr(self, "expires"):
524
self.expires = (datetime.datetime.utcnow()
527
self.last_enabled = None
530
logger.debug("Creating client %r", self.name)
306
531
# Uppercase and remove spaces from fingerprint for later
307
532
# comparison purposes with return value from the fingerprint()
309
self.fingerprint = (config[u"fingerprint"].upper()
311
logger.debug(u" Fingerprint: %s", self.fingerprint)
312
if u"secret" in config:
313
self.secret = config[u"secret"].decode(u"base64")
314
elif u"secfile" in config:
315
with open(os.path.expanduser(os.path.expandvars
316
(config[u"secfile"])),
318
self.secret = secfile.read()
320
raise TypeError(u"No secret or secfile for client %s"
322
self.host = config.get(u"host", u"")
323
self.created = datetime.datetime.utcnow()
325
self.last_approval_request = None
326
self.last_enabled = None
327
self.last_checked_ok = None
328
self.timeout = string_to_delta(config[u"timeout"])
329
self.interval = string_to_delta(config[u"interval"])
330
self.disable_hook = disable_hook
534
logger.debug(" Fingerprint: %s", self.fingerprint)
535
self.created = settings.get("created",
536
datetime.datetime.utcnow())
538
# attributes specific for this server instance
331
539
self.checker = None
332
540
self.checker_initiator_tag = None
333
541
self.disable_initiator_tag = None
334
542
self.checker_callback_tag = None
335
self.checker_command = config[u"checker"]
336
543
self.current_checker_command = None
337
self.last_connect = None
338
self._approved = None
339
self.approved_by_default = config.get(u"approved_by_default",
341
545
self.approvals_pending = 0
342
self.approval_delay = string_to_delta(
343
config[u"approval_delay"])
344
self.approval_duration = string_to_delta(
345
config[u"approval_duration"])
346
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
546
self.changedstate = (multiprocessing_manager
547
.Condition(multiprocessing_manager
549
self.client_structure = [attr for attr in
550
self.__dict__.iterkeys()
551
if not attr.startswith("_")]
552
self.client_structure.append("client_structure")
554
for name, t in inspect.getmembers(type(self),
558
if not name.startswith("_"):
559
self.client_structure.append(name)
561
# Send notice to process children that client state has changed
348
562
def send_changedstate(self):
349
self.changedstate.acquire()
350
self.changedstate.notify_all()
351
self.changedstate.release()
563
with self.changedstate:
564
self.changedstate.notify_all()
353
566
def enable(self):
354
567
"""Start this client's checker and timeout hooks"""
355
if getattr(self, u"enabled", False):
568
if getattr(self, "enabled", False):
356
569
# Already enabled
358
self.send_changedstate()
571
self.expires = datetime.datetime.utcnow() + self.timeout
359
573
self.last_enabled = datetime.datetime.utcnow()
575
self.send_changedstate()
577
def disable(self, quiet=True):
578
"""Disable this client."""
579
if not getattr(self, "enabled", False):
582
logger.info("Disabling client %s", self.name)
583
if getattr(self, "disable_initiator_tag", None) is not None:
584
gobject.source_remove(self.disable_initiator_tag)
585
self.disable_initiator_tag = None
587
if getattr(self, "checker_initiator_tag", None) is not None:
588
gobject.source_remove(self.checker_initiator_tag)
589
self.checker_initiator_tag = None
593
self.send_changedstate()
594
# Do not run this again if called by a gobject.timeout_add
600
def init_checker(self):
360
601
# Schedule a new checker to be started an 'interval' from now,
361
602
# and every interval from then on.
603
if self.checker_initiator_tag is not None:
604
gobject.source_remove(self.checker_initiator_tag)
362
605
self.checker_initiator_tag = (gobject.timeout_add
363
(self.interval_milliseconds(),
607
.total_seconds() * 1000),
364
608
self.start_checker))
365
609
# Schedule a disable() when 'timeout' has passed
610
if self.disable_initiator_tag is not None:
611
gobject.source_remove(self.disable_initiator_tag)
366
612
self.disable_initiator_tag = (gobject.timeout_add
367
(self.timeout_milliseconds(),
614
.total_seconds() * 1000),
370
616
# Also start a new checker *right now*.
371
617
self.start_checker()
373
def disable(self, quiet=True):
374
"""Disable this client."""
375
if not getattr(self, "enabled", False):
378
self.send_changedstate()
380
logger.info(u"Disabling client %s", self.name)
381
if getattr(self, u"disable_initiator_tag", False):
382
gobject.source_remove(self.disable_initiator_tag)
383
self.disable_initiator_tag = None
384
if getattr(self, u"checker_initiator_tag", False):
385
gobject.source_remove(self.checker_initiator_tag)
386
self.checker_initiator_tag = None
388
if self.disable_hook:
389
self.disable_hook(self)
391
# Do not run this again if called by a gobject.timeout_add
395
self.disable_hook = None
398
619
def checker_callback(self, pid, condition, command):
399
620
"""The checker has completed, so take appropriate actions."""
400
621
self.checker_callback_tag = None
401
622
self.checker = None
402
623
if os.WIFEXITED(condition):
403
exitstatus = os.WEXITSTATUS(condition)
405
logger.info(u"Checker for %(name)s succeeded",
624
self.last_checker_status = os.WEXITSTATUS(condition)
625
if self.last_checker_status == 0:
626
logger.info("Checker for %(name)s succeeded",
407
628
self.checked_ok()
409
logger.info(u"Checker for %(name)s failed",
630
logger.info("Checker for %(name)s failed",
412
logger.warning(u"Checker for %(name)s crashed?",
633
self.last_checker_status = -1
634
logger.warning("Checker for %(name)s crashed?",
415
637
def checked_ok(self):
416
"""Bump up the timeout for this client.
418
This should only be called when the client has been seen,
638
"""Assert that the client has been seen, alive and well."""
421
639
self.last_checked_ok = datetime.datetime.utcnow()
422
gobject.source_remove(self.disable_initiator_tag)
423
self.disable_initiator_tag = (gobject.timeout_add
424
(self.timeout_milliseconds(),
640
self.last_checker_status = 0
643
def bump_timeout(self, timeout=None):
644
"""Bump up the timeout for this client."""
646
timeout = self.timeout
647
if self.disable_initiator_tag is not None:
648
gobject.source_remove(self.disable_initiator_tag)
649
self.disable_initiator_tag = None
650
if getattr(self, "enabled", False):
651
self.disable_initiator_tag = (gobject.timeout_add
652
(int(timeout.total_seconds()
653
* 1000), self.disable))
654
self.expires = datetime.datetime.utcnow() + timeout
427
656
def need_approval(self):
428
657
self.last_approval_request = datetime.datetime.utcnow()
444
673
# If a checker exists, make sure it is not a zombie
446
675
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
447
except (AttributeError, OSError), error:
448
if (isinstance(error, OSError)
449
and error.errno != errno.ECHILD):
676
except AttributeError:
678
except OSError as error:
679
if error.errno != errno.ECHILD:
453
logger.warning(u"Checker was a zombie")
683
logger.warning("Checker was a zombie")
454
684
gobject.source_remove(self.checker_callback_tag)
455
685
self.checker_callback(pid, status,
456
686
self.current_checker_command)
457
687
# Start a new checker if needed
458
688
if self.checker is None:
689
# Escape attributes for the shell
690
escaped_attrs = { attr:
691
re.escape(unicode(getattr(self,
693
for attr in self.runtime_expansions }
460
# In case checker_command has exactly one % operator
461
command = self.checker_command % self.host
463
# Escape attributes for the shell
464
escaped_attrs = dict(
466
re.escape(unicode(str(getattr(self, attr, u"")),
470
self.runtime_expansions)
473
command = self.checker_command % escaped_attrs
474
except TypeError, error:
475
logger.error(u'Could not format string "%s":'
476
u' %s', self.checker_command, error)
477
return True # Try again later
695
command = self.checker_command % escaped_attrs
696
except TypeError as error:
697
logger.error('Could not format string "%s"',
698
self.checker_command, exc_info=error)
699
return True # Try again later
478
700
self.current_checker_command = command
480
logger.info(u"Starting checker %r for %s",
702
logger.info("Starting checker %r for %s",
481
703
command, self.name)
482
704
# We don't need to redirect stdout and stderr, since
483
705
# in normal mode, that is already done by daemon(),
484
706
# and in debug mode we don't want to. (Stdin is
485
707
# always replaced by /dev/null.)
708
# The exception is when not debugging but nevertheless
709
# running in the foreground; use the previously
712
if (not self.server_settings["debug"]
713
and self.server_settings["foreground"]):
714
popen_args.update({"stdout": wnull,
486
716
self.checker = subprocess.Popen(command,
488
shell=True, cwd=u"/")
489
self.checker_callback_tag = (gobject.child_watch_add
491
self.checker_callback,
493
# The checker may have completed before the gobject
494
# watch was added. Check for this.
720
except OSError as error:
721
logger.error("Failed to start subprocess",
724
self.checker_callback_tag = (gobject.child_watch_add
726
self.checker_callback,
728
# The checker may have completed before the gobject
729
# watch was added. Check for this.
495
731
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
497
gobject.source_remove(self.checker_callback_tag)
498
self.checker_callback(pid, status, command)
499
except OSError, error:
500
logger.error(u"Failed to start subprocess: %s",
732
except OSError as error:
733
if error.errno == errno.ECHILD:
734
# This should never happen
735
logger.error("Child process vanished",
740
gobject.source_remove(self.checker_callback_tag)
741
self.checker_callback(pid, status, command)
502
742
# Re-run this periodically if run by gobject.timeout_add
572
850
class DBusObjectWithProperties(dbus.service.Object):
573
851
"""A D-Bus object with properties.
575
853
Classes inheriting from this can use the dbus_service_property
576
854
decorator to expose methods as D-Bus properties. It exposes the
577
855
standard Get(), Set(), and GetAll() methods on the D-Bus.
581
def _is_dbus_property(obj):
582
return getattr(obj, u"_dbus_is_property", False)
859
def _is_dbus_thing(thing):
860
"""Returns a function testing if an attribute is a D-Bus thing
862
If called like _is_dbus_thing("method") it returns a function
863
suitable for use as predicate to inspect.getmembers().
865
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
584
def _get_all_dbus_properties(self):
868
def _get_all_dbus_things(self, thing):
585
869
"""Returns a generator of (name, attribute) pairs
587
return ((prop._dbus_name, prop)
589
inspect.getmembers(self, self._is_dbus_property))
871
return ((getattr(athing.__get__(self), "_dbus_name",
873
athing.__get__(self))
874
for cls in self.__class__.__mro__
876
inspect.getmembers(cls,
877
self._is_dbus_thing(thing)))
591
879
def _get_dbus_property(self, interface_name, property_name):
592
880
"""Returns a bound method if one exists which is a D-Bus
593
881
property with the specified name and interface.
595
for name in (property_name,
596
property_name + u"_dbus_property"):
597
prop = getattr(self, name, None)
599
or not self._is_dbus_property(prop)
600
or prop._dbus_name != property_name
601
or (interface_name and prop._dbus_interface
602
and interface_name != prop._dbus_interface)):
883
for cls in self.__class__.__mro__:
884
for name, value in (inspect.getmembers
886
self._is_dbus_thing("property"))):
887
if (value._dbus_name == property_name
888
and value._dbus_interface == interface_name):
889
return value.__get__(self)
605
891
# No such property
606
raise DBusPropertyNotFound(self.dbus_object_path + u":"
607
+ interface_name + u"."
892
raise DBusPropertyNotFound(self.dbus_object_path + ":"
893
+ interface_name + "."
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
896
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
612
898
def Get(self, interface_name, property_name):
613
899
"""Standard D-Bus property Get() method, see D-Bus standard.
615
901
prop = self._get_dbus_property(interface_name, property_name)
616
if prop._dbus_access == u"write":
902
if prop._dbus_access == "write":
617
903
raise DBusPropertyAccessException(property_name)
619
if not hasattr(value, u"variant_level"):
905
if not hasattr(value, "variant_level"):
621
907
return type(value)(value, variant_level=value.variant_level+1)
623
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
909
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
624
910
def Set(self, interface_name, property_name, value):
625
911
"""Standard D-Bus property Set() method, see D-Bus standard.
627
913
prop = self._get_dbus_property(interface_name, property_name)
628
if prop._dbus_access == u"read":
914
if prop._dbus_access == "read":
629
915
raise DBusPropertyAccessException(property_name)
630
if prop._dbus_get_args_options[u"byte_arrays"]:
916
if prop._dbus_get_args_options["byte_arrays"]:
631
917
# The byte_arrays option is not supported yet on
632
918
# signatures other than "ay".
633
if prop._dbus_signature != u"ay":
635
value = dbus.ByteArray(''.join(unichr(byte)
919
if prop._dbus_signature != "ay":
920
raise ValueError("Byte arrays not supported for non-"
921
"'ay' signature {!r}"
922
.format(prop._dbus_signature))
923
value = dbus.ByteArray(b''.join(chr(byte)
639
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
640
out_signature=u"a{sv}")
927
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
928
out_signature="a{sv}")
641
929
def GetAll(self, interface_name):
642
930
"""Standard D-Bus property GetAll() method, see D-Bus
645
933
Note: Will not include properties with access="write".
648
for name, prop in self._get_all_dbus_properties():
936
for name, prop in self._get_all_dbus_things("property"):
649
937
if (interface_name
650
938
and interface_name != prop._dbus_interface):
651
939
# Interface non-empty but did not match
653
941
# Ignore write-only properties
654
if prop._dbus_access == u"write":
942
if prop._dbus_access == "write":
657
if not hasattr(value, u"variant_level"):
945
if not hasattr(value, "variant_level"):
946
properties[name] = value
660
all[name] = type(value)(value, variant_level=
661
value.variant_level+1)
662
return dbus.Dictionary(all, signature=u"sv")
948
properties[name] = type(value)(value, variant_level=
949
value.variant_level+1)
950
return dbus.Dictionary(properties, signature="sv")
664
952
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
666
954
path_keyword='object_path',
667
955
connection_keyword='connection')
668
956
def Introspect(self, object_path, connection):
669
"""Standard D-Bus method, overloaded to insert property tags.
957
"""Overloading of standard D-Bus method.
959
Inserts property tags and interface annotation tags.
671
961
xmlstring = dbus.service.Object.Introspect(self, object_path,
674
964
document = xml.dom.minidom.parseString(xmlstring)
675
965
def make_tag(document, name, prop):
676
e = document.createElement(u"property")
677
e.setAttribute(u"name", name)
678
e.setAttribute(u"type", prop._dbus_signature)
679
e.setAttribute(u"access", prop._dbus_access)
966
e = document.createElement("property")
967
e.setAttribute("name", name)
968
e.setAttribute("type", prop._dbus_signature)
969
e.setAttribute("access", prop._dbus_access)
681
for if_tag in document.getElementsByTagName(u"interface"):
971
for if_tag in document.getElementsByTagName("interface"):
682
973
for tag in (make_tag(document, name, prop)
684
in self._get_all_dbus_properties()
975
in self._get_all_dbus_things("property")
685
976
if prop._dbus_interface
686
== if_tag.getAttribute(u"name")):
977
== if_tag.getAttribute("name")):
687
978
if_tag.appendChild(tag)
979
# Add annotation tags
980
for typ in ("method", "signal", "property"):
981
for tag in if_tag.getElementsByTagName(typ):
983
for name, prop in (self.
984
_get_all_dbus_things(typ)):
985
if (name == tag.getAttribute("name")
986
and prop._dbus_interface
987
== if_tag.getAttribute("name")):
988
annots.update(getattr
992
for name, value in annots.items():
993
ann_tag = document.createElement(
995
ann_tag.setAttribute("name", name)
996
ann_tag.setAttribute("value", value)
997
tag.appendChild(ann_tag)
998
# Add interface annotation tags
999
for annotation, value in dict(
1000
itertools.chain.from_iterable(
1001
annotations().items()
1002
for name, annotations in
1003
self._get_all_dbus_things("interface")
1004
if name == if_tag.getAttribute("name")
1006
ann_tag = document.createElement("annotation")
1007
ann_tag.setAttribute("name", annotation)
1008
ann_tag.setAttribute("value", value)
1009
if_tag.appendChild(ann_tag)
688
1010
# Add the names to the return values for the
689
1011
# "org.freedesktop.DBus.Properties" methods
690
if (if_tag.getAttribute(u"name")
691
== u"org.freedesktop.DBus.Properties"):
692
for cn in if_tag.getElementsByTagName(u"method"):
693
if cn.getAttribute(u"name") == u"Get":
694
for arg in cn.getElementsByTagName(u"arg"):
695
if (arg.getAttribute(u"direction")
697
arg.setAttribute(u"name", u"value")
698
elif cn.getAttribute(u"name") == u"GetAll":
699
for arg in cn.getElementsByTagName(u"arg"):
700
if (arg.getAttribute(u"direction")
702
arg.setAttribute(u"name", u"props")
703
xmlstring = document.toxml(u"utf-8")
1012
if (if_tag.getAttribute("name")
1013
== "org.freedesktop.DBus.Properties"):
1014
for cn in if_tag.getElementsByTagName("method"):
1015
if cn.getAttribute("name") == "Get":
1016
for arg in cn.getElementsByTagName("arg"):
1017
if (arg.getAttribute("direction")
1019
arg.setAttribute("name", "value")
1020
elif cn.getAttribute("name") == "GetAll":
1021
for arg in cn.getElementsByTagName("arg"):
1022
if (arg.getAttribute("direction")
1024
arg.setAttribute("name", "props")
1025
xmlstring = document.toxml("utf-8")
704
1026
document.unlink()
705
1027
except (AttributeError, xml.dom.DOMException,
706
xml.parsers.expat.ExpatError), error:
707
logger.error(u"Failed to override Introspection method",
1028
xml.parsers.expat.ExpatError) as error:
1029
logger.error("Failed to override Introspection method",
709
1031
return xmlstring
1034
def datetime_to_dbus(dt, variant_level=0):
1035
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1037
return dbus.String("", variant_level = variant_level)
1038
return dbus.String(dt.isoformat(),
1039
variant_level=variant_level)
1042
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1043
"""A class decorator; applied to a subclass of
1044
dbus.service.Object, it will add alternate D-Bus attributes with
1045
interface names according to the "alt_interface_names" mapping.
1048
@alternate_dbus_interfaces({"org.example.Interface":
1049
"net.example.AlternateInterface"})
1050
class SampleDBusObject(dbus.service.Object):
1051
@dbus.service.method("org.example.Interface")
1052
def SampleDBusMethod():
1055
The above "SampleDBusMethod" on "SampleDBusObject" will be
1056
reachable via two interfaces: "org.example.Interface" and
1057
"net.example.AlternateInterface", the latter of which will have
1058
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1059
"true", unless "deprecate" is passed with a False value.
1061
This works for methods and signals, and also for D-Bus properties
1062
(from DBusObjectWithProperties) and interfaces (from the
1063
dbus_interface_annotations decorator).
1066
for orig_interface_name, alt_interface_name in (
1067
alt_interface_names.items()):
1069
interface_names = set()
1070
# Go though all attributes of the class
1071
for attrname, attribute in inspect.getmembers(cls):
1072
# Ignore non-D-Bus attributes, and D-Bus attributes
1073
# with the wrong interface name
1074
if (not hasattr(attribute, "_dbus_interface")
1075
or not attribute._dbus_interface
1076
.startswith(orig_interface_name)):
1078
# Create an alternate D-Bus interface name based on
1080
alt_interface = (attribute._dbus_interface
1081
.replace(orig_interface_name,
1082
alt_interface_name))
1083
interface_names.add(alt_interface)
1084
# Is this a D-Bus signal?
1085
if getattr(attribute, "_dbus_is_signal", False):
1086
# Extract the original non-method undecorated
1087
# function by black magic
1088
nonmethod_func = (dict(
1089
zip(attribute.func_code.co_freevars,
1090
attribute.__closure__))["func"]
1092
# Create a new, but exactly alike, function
1093
# object, and decorate it to be a new D-Bus signal
1094
# with the alternate D-Bus interface name
1095
new_function = (dbus.service.signal
1097
attribute._dbus_signature)
1098
(types.FunctionType(
1099
nonmethod_func.func_code,
1100
nonmethod_func.func_globals,
1101
nonmethod_func.func_name,
1102
nonmethod_func.func_defaults,
1103
nonmethod_func.func_closure)))
1104
# Copy annotations, if any
1106
new_function._dbus_annotations = (
1107
dict(attribute._dbus_annotations))
1108
except AttributeError:
1110
# Define a creator of a function to call both the
1111
# original and alternate functions, so both the
1112
# original and alternate signals gets sent when
1113
# the function is called
1114
def fixscope(func1, func2):
1115
"""This function is a scope container to pass
1116
func1 and func2 to the "call_both" function
1117
outside of its arguments"""
1118
def call_both(*args, **kwargs):
1119
"""This function will emit two D-Bus
1120
signals by calling func1 and func2"""
1121
func1(*args, **kwargs)
1122
func2(*args, **kwargs)
1124
# Create the "call_both" function and add it to
1126
attr[attrname] = fixscope(attribute, new_function)
1127
# Is this a D-Bus method?
1128
elif getattr(attribute, "_dbus_is_method", False):
1129
# Create a new, but exactly alike, function
1130
# object. Decorate it to be a new D-Bus method
1131
# with the alternate D-Bus interface name. Add it
1133
attr[attrname] = (dbus.service.method
1135
attribute._dbus_in_signature,
1136
attribute._dbus_out_signature)
1138
(attribute.func_code,
1139
attribute.func_globals,
1140
attribute.func_name,
1141
attribute.func_defaults,
1142
attribute.func_closure)))
1143
# Copy annotations, if any
1145
attr[attrname]._dbus_annotations = (
1146
dict(attribute._dbus_annotations))
1147
except AttributeError:
1149
# Is this a D-Bus property?
1150
elif getattr(attribute, "_dbus_is_property", False):
1151
# Create a new, but exactly alike, function
1152
# object, and decorate it to be a new D-Bus
1153
# property with the alternate D-Bus interface
1154
# name. Add it to the class.
1155
attr[attrname] = (dbus_service_property
1157
attribute._dbus_signature,
1158
attribute._dbus_access,
1160
._dbus_get_args_options
1163
(attribute.func_code,
1164
attribute.func_globals,
1165
attribute.func_name,
1166
attribute.func_defaults,
1167
attribute.func_closure)))
1168
# Copy annotations, if any
1170
attr[attrname]._dbus_annotations = (
1171
dict(attribute._dbus_annotations))
1172
except AttributeError:
1174
# Is this a D-Bus interface?
1175
elif getattr(attribute, "_dbus_is_interface", False):
1176
# Create a new, but exactly alike, function
1177
# object. Decorate it to be a new D-Bus interface
1178
# with the alternate D-Bus interface name. Add it
1180
attr[attrname] = (dbus_interface_annotations
1183
(attribute.func_code,
1184
attribute.func_globals,
1185
attribute.func_name,
1186
attribute.func_defaults,
1187
attribute.func_closure)))
1189
# Deprecate all alternate interfaces
1190
iname="_AlternateDBusNames_interface_annotation{}"
1191
for interface_name in interface_names:
1192
@dbus_interface_annotations(interface_name)
1194
return { "org.freedesktop.DBus.Deprecated":
1196
# Find an unused name
1197
for aname in (iname.format(i)
1198
for i in itertools.count()):
1199
if aname not in attr:
1203
# Replace the class with a new subclass of it with
1204
# methods, signals, etc. as created above.
1205
cls = type(b"{}Alternate".format(cls.__name__),
1211
@alternate_dbus_interfaces({"se.recompile.Mandos":
1212
"se.bsnet.fukt.Mandos"})
712
1213
class ClientDBus(Client, DBusObjectWithProperties):
713
1214
"""A Client class using D-Bus
720
1221
runtime_expansions = (Client.runtime_expansions
721
+ (u"dbus_object_path",))
1222
+ ("dbus_object_path",))
723
1224
# dbus.service.Object doesn't use super(), so we can't either.
725
1226
def __init__(self, bus = None, *args, **kwargs):
726
self._approvals_pending = 0
728
1228
Client.__init__(self, *args, **kwargs)
729
1229
# Only now, when this client is initialized, can it show up on
731
1231
client_object_name = unicode(self.name).translate(
732
{ord(u"."): ord(u"_"),
733
ord(u"-"): ord(u"_")})
1232
{ord("."): ord("_"),
1233
ord("-"): ord("_")})
734
1234
self.dbus_object_path = (dbus.ObjectPath
735
(u"/clients/" + client_object_name))
1235
("/clients/" + client_object_name))
736
1236
DBusObjectWithProperties.__init__(self, self.bus,
737
1237
self.dbus_object_path)
739
def _get_approvals_pending(self):
740
return self._approvals_pending
741
def _set_approvals_pending(self, value):
742
old_value = self._approvals_pending
743
self._approvals_pending = value
745
if (hasattr(self, "dbus_object_path")
746
and bval is not bool(old_value)):
747
dbus_bool = dbus.Boolean(bval, variant_level=1)
748
self.PropertyChanged(dbus.String(u"ApprovalPending"),
751
approvals_pending = property(_get_approvals_pending,
752
_set_approvals_pending)
753
del _get_approvals_pending, _set_approvals_pending
756
def _datetime_to_dbus(dt, variant_level=0):
757
"""Convert a UTC datetime.datetime() to a D-Bus type."""
758
return dbus.String(dt.isoformat(),
759
variant_level=variant_level)
762
oldstate = getattr(self, u"enabled", False)
763
r = Client.enable(self)
764
if oldstate != self.enabled:
766
self.PropertyChanged(dbus.String(u"Enabled"),
767
dbus.Boolean(True, variant_level=1))
768
self.PropertyChanged(
769
dbus.String(u"LastEnabled"),
770
self._datetime_to_dbus(self.last_enabled,
774
def disable(self, quiet = False):
775
oldstate = getattr(self, u"enabled", False)
776
r = Client.disable(self, quiet=quiet)
777
if not quiet and oldstate != self.enabled:
779
self.PropertyChanged(dbus.String(u"Enabled"),
780
dbus.Boolean(False, variant_level=1))
1239
def notifychangeproperty(transform_func,
1240
dbus_name, type_func=lambda x: x,
1242
""" Modify a variable so that it's a property which announces
1243
its changes to DBus.
1245
transform_fun: Function that takes a value and a variant_level
1246
and transforms it to a D-Bus type.
1247
dbus_name: D-Bus name of the variable
1248
type_func: Function that transform the value before sending it
1249
to the D-Bus. Default: no transform
1250
variant_level: D-Bus variant level. Default: 1
1252
attrname = "_{}".format(dbus_name)
1253
def setter(self, value):
1254
if hasattr(self, "dbus_object_path"):
1255
if (not hasattr(self, attrname) or
1256
type_func(getattr(self, attrname, None))
1257
!= type_func(value)):
1258
dbus_value = transform_func(type_func(value),
1261
self.PropertyChanged(dbus.String(dbus_name),
1263
setattr(self, attrname, value)
1265
return property(lambda self: getattr(self, attrname), setter)
1267
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1268
approvals_pending = notifychangeproperty(dbus.Boolean,
1271
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1272
last_enabled = notifychangeproperty(datetime_to_dbus,
1274
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1275
type_func = lambda checker:
1276
checker is not None)
1277
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1279
last_checker_status = notifychangeproperty(dbus.Int16,
1280
"LastCheckerStatus")
1281
last_approval_request = notifychangeproperty(
1282
datetime_to_dbus, "LastApprovalRequest")
1283
approved_by_default = notifychangeproperty(dbus.Boolean,
1284
"ApprovedByDefault")
1285
approval_delay = notifychangeproperty(dbus.UInt64,
1288
lambda td: td.total_seconds()
1290
approval_duration = notifychangeproperty(
1291
dbus.UInt64, "ApprovalDuration",
1292
type_func = lambda td: td.total_seconds() * 1000)
1293
host = notifychangeproperty(dbus.String, "Host")
1294
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1295
type_func = lambda td:
1296
td.total_seconds() * 1000)
1297
extended_timeout = notifychangeproperty(
1298
dbus.UInt64, "ExtendedTimeout",
1299
type_func = lambda td: td.total_seconds() * 1000)
1300
interval = notifychangeproperty(dbus.UInt64,
1303
lambda td: td.total_seconds()
1305
checker_command = notifychangeproperty(dbus.String, "Checker")
1307
del notifychangeproperty
783
1309
def __del__(self, *args, **kwargs):
785
1311
self.remove_from_connection()
786
1312
except LookupError:
788
if hasattr(DBusObjectWithProperties, u"__del__"):
1314
if hasattr(DBusObjectWithProperties, "__del__"):
789
1315
DBusObjectWithProperties.__del__(self, *args, **kwargs)
790
1316
Client.__del__(self, *args, **kwargs)
811
1334
return Client.checker_callback(self, pid, condition, command,
812
1335
*args, **kwargs)
814
def checked_ok(self, *args, **kwargs):
815
r = Client.checked_ok(self, *args, **kwargs)
817
self.PropertyChanged(
818
dbus.String(u"LastCheckedOK"),
819
(self._datetime_to_dbus(self.last_checked_ok,
823
def need_approval(self, *args, **kwargs):
824
r = Client.need_approval(self, *args, **kwargs)
826
self.PropertyChanged(
827
dbus.String(u"LastApprovalRequest"),
828
(self._datetime_to_dbus(self.last_approval_request,
832
1337
def start_checker(self, *args, **kwargs):
833
old_checker = self.checker
834
if self.checker is not None:
835
old_checker_pid = self.checker.pid
837
old_checker_pid = None
1338
old_checker_pid = getattr(self.checker, "pid", None)
838
1339
r = Client.start_checker(self, *args, **kwargs)
839
1340
# Only if new checker process was started
840
1341
if (self.checker is not None
841
1342
and old_checker_pid != self.checker.pid):
842
1343
# Emit D-Bus signal
843
1344
self.CheckerStarted(self.current_checker_command)
844
self.PropertyChanged(
845
dbus.String(u"CheckerRunning"),
846
dbus.Boolean(True, variant_level=1))
849
def stop_checker(self, *args, **kwargs):
850
old_checker = getattr(self, u"checker", None)
851
r = Client.stop_checker(self, *args, **kwargs)
852
if (old_checker is not None
853
and getattr(self, u"checker", None) is None):
854
self.PropertyChanged(dbus.String(u"CheckerRunning"),
855
dbus.Boolean(False, variant_level=1))
858
1347
def _reset_approved(self):
859
self._approved = None
1348
self.approved = None
862
1351
def approve(self, value=True):
1352
self.approved = value
1353
gobject.timeout_add(int(self.approval_duration.total_seconds()
1354
* 1000), self._reset_approved)
863
1355
self.send_changedstate()
864
self._approved = value
865
gobject.timeout_add(self._timedelta_to_milliseconds
866
(self.approval_duration),
867
self._reset_approved)
870
1357
## D-Bus methods, signals & properties
871
_interface = u"se.bsnet.fukt.Mandos.Client"
1358
_interface = "se.recompile.Mandos.Client"
1362
@dbus_interface_annotations(_interface)
1364
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
875
1369
# CheckerCompleted - signal
876
@dbus.service.signal(_interface, signature=u"nxs")
1370
@dbus.service.signal(_interface, signature="nxs")
877
1371
def CheckerCompleted(self, exitcode, waitstatus, command):
881
1375
# CheckerStarted - signal
882
@dbus.service.signal(_interface, signature=u"s")
1376
@dbus.service.signal(_interface, signature="s")
883
1377
def CheckerStarted(self, command):
887
1381
# PropertyChanged - signal
888
@dbus.service.signal(_interface, signature=u"sv")
1382
@dbus.service.signal(_interface, signature="sv")
889
1383
def PropertyChanged(self, property, value):
951
1445
# ApprovalPending - property
952
@dbus_service_property(_interface, signature=u"b", access=u"read")
1446
@dbus_service_property(_interface, signature="b", access="read")
953
1447
def ApprovalPending_dbus_property(self):
954
1448
return dbus.Boolean(bool(self.approvals_pending))
956
1450
# ApprovedByDefault - property
957
@dbus_service_property(_interface, signature=u"b",
1451
@dbus_service_property(_interface, signature="b",
959
1453
def ApprovedByDefault_dbus_property(self, value=None):
960
1454
if value is None: # get
961
1455
return dbus.Boolean(self.approved_by_default)
962
1456
self.approved_by_default = bool(value)
964
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
965
dbus.Boolean(value, variant_level=1))
967
1458
# ApprovalDelay - property
968
@dbus_service_property(_interface, signature=u"t",
1459
@dbus_service_property(_interface, signature="t",
970
1461
def ApprovalDelay_dbus_property(self, value=None):
971
1462
if value is None: # get
972
return dbus.UInt64(self.approval_delay_milliseconds())
1463
return dbus.UInt64(self.approval_delay.total_seconds()
973
1465
self.approval_delay = datetime.timedelta(0, 0, 0, value)
975
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
976
dbus.UInt64(value, variant_level=1))
978
1467
# ApprovalDuration - property
979
@dbus_service_property(_interface, signature=u"t",
1468
@dbus_service_property(_interface, signature="t",
981
1470
def ApprovalDuration_dbus_property(self, value=None):
982
1471
if value is None: # get
983
return dbus.UInt64(self._timedelta_to_milliseconds(
984
self.approval_duration))
1472
return dbus.UInt64(self.approval_duration.total_seconds()
985
1474
self.approval_duration = datetime.timedelta(0, 0, 0, value)
987
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
988
dbus.UInt64(value, variant_level=1))
990
1476
# Name - property
991
@dbus_service_property(_interface, signature=u"s", access=u"read")
1477
@dbus_service_property(_interface, signature="s", access="read")
992
1478
def Name_dbus_property(self):
993
1479
return dbus.String(self.name)
995
1481
# Fingerprint - property
996
@dbus_service_property(_interface, signature=u"s", access=u"read")
1482
@dbus_service_property(_interface, signature="s", access="read")
997
1483
def Fingerprint_dbus_property(self):
998
1484
return dbus.String(self.fingerprint)
1000
1486
# Host - property
1001
@dbus_service_property(_interface, signature=u"s",
1002
access=u"readwrite")
1487
@dbus_service_property(_interface, signature="s",
1003
1489
def Host_dbus_property(self, value=None):
1004
1490
if value is None: # get
1005
1491
return dbus.String(self.host)
1008
self.PropertyChanged(dbus.String(u"Host"),
1009
dbus.String(value, variant_level=1))
1492
self.host = unicode(value)
1011
1494
# Created - property
1012
@dbus_service_property(_interface, signature=u"s", access=u"read")
1495
@dbus_service_property(_interface, signature="s", access="read")
1013
1496
def Created_dbus_property(self):
1014
return dbus.String(self._datetime_to_dbus(self.created))
1497
return datetime_to_dbus(self.created)
1016
1499
# LastEnabled - property
1017
@dbus_service_property(_interface, signature=u"s", access=u"read")
1500
@dbus_service_property(_interface, signature="s", access="read")
1018
1501
def LastEnabled_dbus_property(self):
1019
if self.last_enabled is None:
1020
return dbus.String(u"")
1021
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1502
return datetime_to_dbus(self.last_enabled)
1023
1504
# Enabled - property
1024
@dbus_service_property(_interface, signature=u"b",
1025
access=u"readwrite")
1505
@dbus_service_property(_interface, signature="b",
1026
1507
def Enabled_dbus_property(self, value=None):
1027
1508
if value is None: # get
1028
1509
return dbus.Boolean(self.enabled)
1034
1515
# LastCheckedOK - property
1035
@dbus_service_property(_interface, signature=u"s",
1036
access=u"readwrite")
1516
@dbus_service_property(_interface, signature="s",
1037
1518
def LastCheckedOK_dbus_property(self, value=None):
1038
1519
if value is not None:
1039
1520
self.checked_ok()
1041
if self.last_checked_ok is None:
1042
return dbus.String(u"")
1043
return dbus.String(self._datetime_to_dbus(self
1522
return datetime_to_dbus(self.last_checked_ok)
1524
# LastCheckerStatus - property
1525
@dbus_service_property(_interface, signature="n",
1527
def LastCheckerStatus_dbus_property(self):
1528
return dbus.Int16(self.last_checker_status)
1530
# Expires - property
1531
@dbus_service_property(_interface, signature="s", access="read")
1532
def Expires_dbus_property(self):
1533
return datetime_to_dbus(self.expires)
1046
1535
# LastApprovalRequest - property
1047
@dbus_service_property(_interface, signature=u"s", access=u"read")
1536
@dbus_service_property(_interface, signature="s", access="read")
1048
1537
def LastApprovalRequest_dbus_property(self):
1049
if self.last_approval_request is None:
1050
return dbus.String(u"")
1051
return dbus.String(self.
1052
_datetime_to_dbus(self
1053
.last_approval_request))
1538
return datetime_to_dbus(self.last_approval_request)
1055
1540
# Timeout - property
1056
@dbus_service_property(_interface, signature=u"t",
1057
access=u"readwrite")
1541
@dbus_service_property(_interface, signature="t",
1058
1543
def Timeout_dbus_property(self, value=None):
1059
1544
if value is None: # get
1060
return dbus.UInt64(self.timeout_milliseconds())
1545
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1546
old_timeout = self.timeout
1061
1547
self.timeout = datetime.timedelta(0, 0, 0, value)
1063
self.PropertyChanged(dbus.String(u"Timeout"),
1064
dbus.UInt64(value, variant_level=1))
1065
if getattr(self, u"disable_initiator_tag", None) is None:
1067
# Reschedule timeout
1068
gobject.source_remove(self.disable_initiator_tag)
1069
self.disable_initiator_tag = None
1070
time_to_die = (self.
1071
_timedelta_to_milliseconds((self
1076
if time_to_die <= 0:
1077
# The timeout has passed
1080
self.disable_initiator_tag = (gobject.timeout_add
1081
(time_to_die, self.disable))
1548
# Reschedule disabling
1550
now = datetime.datetime.utcnow()
1551
self.expires += self.timeout - old_timeout
1552
if self.expires <= now:
1553
# The timeout has passed
1556
if (getattr(self, "disable_initiator_tag", None)
1559
gobject.source_remove(self.disable_initiator_tag)
1560
self.disable_initiator_tag = (
1561
gobject.timeout_add(
1562
int((self.expires - now).total_seconds()
1563
* 1000), self.disable))
1565
# ExtendedTimeout - property
1566
@dbus_service_property(_interface, signature="t",
1568
def ExtendedTimeout_dbus_property(self, value=None):
1569
if value is None: # get
1570
return dbus.UInt64(self.extended_timeout.total_seconds()
1572
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1083
1574
# Interval - property
1084
@dbus_service_property(_interface, signature=u"t",
1085
access=u"readwrite")
1575
@dbus_service_property(_interface, signature="t",
1086
1577
def Interval_dbus_property(self, value=None):
1087
1578
if value is None: # get
1088
return dbus.UInt64(self.interval_milliseconds())
1579
return dbus.UInt64(self.interval.total_seconds() * 1000)
1089
1580
self.interval = datetime.timedelta(0, 0, 0, value)
1091
self.PropertyChanged(dbus.String(u"Interval"),
1092
dbus.UInt64(value, variant_level=1))
1093
if getattr(self, u"checker_initiator_tag", None) is None:
1581
if getattr(self, "checker_initiator_tag", None) is None:
1095
# Reschedule checker run
1096
gobject.source_remove(self.checker_initiator_tag)
1097
self.checker_initiator_tag = (gobject.timeout_add
1098
(value, self.start_checker))
1099
self.start_checker() # Start one now, too
1584
# Reschedule checker run
1585
gobject.source_remove(self.checker_initiator_tag)
1586
self.checker_initiator_tag = (gobject.timeout_add
1587
(value, self.start_checker))
1588
self.start_checker() # Start one now, too
1101
1590
# Checker - property
1102
@dbus_service_property(_interface, signature=u"s",
1103
access=u"readwrite")
1591
@dbus_service_property(_interface, signature="s",
1104
1593
def Checker_dbus_property(self, value=None):
1105
1594
if value is None: # get
1106
1595
return dbus.String(self.checker_command)
1107
self.checker_command = value
1109
self.PropertyChanged(dbus.String(u"Checker"),
1110
dbus.String(self.checker_command,
1596
self.checker_command = unicode(value)
1113
1598
# CheckerRunning - property
1114
@dbus_service_property(_interface, signature=u"b",
1115
access=u"readwrite")
1599
@dbus_service_property(_interface, signature="b",
1116
1601
def CheckerRunning_dbus_property(self, value=None):
1117
1602
if value is None: # get
1118
1603
return dbus.Boolean(self.checker is not None)
1170
1655
def handle(self):
1171
1656
with contextlib.closing(self.server.child_pipe) as child_pipe:
1172
logger.info(u"TCP connection from: %s",
1657
logger.info("TCP connection from: %s",
1173
1658
unicode(self.client_address))
1174
logger.debug(u"Pipe FD: %d",
1659
logger.debug("Pipe FD: %d",
1175
1660
self.server.child_pipe.fileno())
1177
1662
session = (gnutls.connection
1178
1663
.ClientSession(self.request,
1179
1664
gnutls.connection
1180
1665
.X509Credentials()))
1182
1667
# Note: gnutls.connection.X509Credentials is really a
1183
1668
# generic GnuTLS certificate credentials object so long as
1184
1669
# no X.509 keys are added to it. Therefore, we can use it
1185
1670
# here despite using OpenPGP certificates.
1187
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1188
# u"+AES-256-CBC", u"+SHA1",
1189
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1672
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1673
# "+AES-256-CBC", "+SHA1",
1674
# "+COMP-NULL", "+CTYPE-OPENPGP",
1191
1676
# Use a fallback default, since this MUST be set.
1192
1677
priority = self.server.gnutls_priority
1193
1678
if priority is None:
1194
priority = u"NORMAL"
1195
1680
(gnutls.library.functions
1196
1681
.gnutls_priority_set_direct(session._c_object,
1197
1682
priority, None))
1199
1684
# Start communication using the Mandos protocol
1200
1685
# Get protocol number
1201
1686
line = self.request.makefile().readline()
1202
logger.debug(u"Protocol version: %r", line)
1687
logger.debug("Protocol version: %r", line)
1204
1689
if int(line.strip().split()[0]) > 1:
1206
except (ValueError, IndexError, RuntimeError), error:
1207
logger.error(u"Unknown protocol version: %s", error)
1690
raise RuntimeError(line)
1691
except (ValueError, IndexError, RuntimeError) as error:
1692
logger.error("Unknown protocol version: %s", error)
1210
1695
# Start GnuTLS connection
1212
1697
session.handshake()
1213
except gnutls.errors.GNUTLSError, error:
1214
logger.warning(u"Handshake failed: %s", error)
1698
except gnutls.errors.GNUTLSError as error:
1699
logger.warning("Handshake failed: %s", error)
1215
1700
# Do not run session.bye() here: the session is not
1216
1701
# established. Just abandon the request.
1218
logger.debug(u"Handshake succeeded")
1703
logger.debug("Handshake succeeded")
1220
1705
approval_required = False
1223
1708
fpr = self.fingerprint(self.peer_certificate
1225
except (TypeError, gnutls.errors.GNUTLSError), error:
1226
logger.warning(u"Bad certificate: %s", error)
1711
gnutls.errors.GNUTLSError) as error:
1712
logger.warning("Bad certificate: %s", error)
1228
logger.debug(u"Fingerprint: %s", fpr)
1714
logger.debug("Fingerprint: %s", fpr)
1231
1717
client = ProxyClient(child_pipe, fpr,
1232
1718
self.client_address)
1420
1911
use_ipv6: Boolean; to use IPv6 or not
1422
1913
def __init__(self, server_address, RequestHandlerClass,
1423
interface=None, use_ipv6=True):
1914
interface=None, use_ipv6=True, socketfd=None):
1915
"""If socketfd is set, use that file descriptor instead of
1916
creating a new one with socket.socket().
1424
1918
self.interface = interface
1426
1920
self.address_family = socket.AF_INET6
1921
if socketfd is not None:
1922
# Save the file descriptor
1923
self.socketfd = socketfd
1924
# Save the original socket.socket() function
1925
self.socket_socket = socket.socket
1926
# To implement --socket, we monkey patch socket.socket.
1928
# (When socketserver.TCPServer is a new-style class, we
1929
# could make self.socket into a property instead of monkey
1930
# patching socket.socket.)
1932
# Create a one-time-only replacement for socket.socket()
1933
@functools.wraps(socket.socket)
1934
def socket_wrapper(*args, **kwargs):
1935
# Restore original function so subsequent calls are
1937
socket.socket = self.socket_socket
1938
del self.socket_socket
1939
# This time only, return a new socket object from the
1940
# saved file descriptor.
1941
return socket.fromfd(self.socketfd, *args, **kwargs)
1942
# Replace socket.socket() function with wrapper
1943
socket.socket = socket_wrapper
1944
# The socketserver.TCPServer.__init__ will call
1945
# socket.socket(), which might be our replacement,
1946
# socket_wrapper(), if socketfd was set.
1427
1947
socketserver.TCPServer.__init__(self, server_address,
1428
1948
RequestHandlerClass)
1429
1950
def server_bind(self):
1430
1951
"""This overrides the normal server_bind() function
1431
1952
to bind to an interface if one was specified, and also NOT to
1432
1953
bind to an address or port if they were not specified."""
1433
1954
if self.interface is not None:
1434
1955
if SO_BINDTODEVICE is None:
1435
logger.error(u"SO_BINDTODEVICE does not exist;"
1436
u" cannot bind to interface %s",
1956
logger.error("SO_BINDTODEVICE does not exist;"
1957
" cannot bind to interface %s",
1437
1958
self.interface)
1440
1961
self.socket.setsockopt(socket.SOL_SOCKET,
1441
1962
SO_BINDTODEVICE,
1444
except socket.error, error:
1445
if error[0] == errno.EPERM:
1446
logger.error(u"No permission to"
1447
u" bind to interface %s",
1449
elif error[0] == errno.ENOPROTOOPT:
1450
logger.error(u"SO_BINDTODEVICE not available;"
1451
u" cannot bind to interface %s",
1963
str(self.interface + '\0'))
1964
except socket.error as error:
1965
if error.errno == errno.EPERM:
1966
logger.error("No permission to bind to"
1967
" interface %s", self.interface)
1968
elif error.errno == errno.ENOPROTOOPT:
1969
logger.error("SO_BINDTODEVICE not available;"
1970
" cannot bind to interface %s",
1972
elif error.errno == errno.ENODEV:
1973
logger.error("Interface %s does not exist,"
1974
" cannot bind", self.interface)
1455
1977
# Only bind(2) the socket if we really need to.
1456
1978
if self.server_address[0] or self.server_address[1]:
1457
1979
if not self.server_address[0]:
1458
1980
if self.address_family == socket.AF_INET6:
1459
any_address = u"::" # in6addr_any
1981
any_address = "::" # in6addr_any
1461
any_address = socket.INADDR_ANY
1983
any_address = "0.0.0.0" # INADDR_ANY
1462
1984
self.server_address = (any_address,
1463
1985
self.server_address[1])
1464
1986
elif not self.server_address[1]:
1536
2051
fpr = request[1]
1537
2052
address = request[2]
1539
for c in self.clients:
2054
for c in self.clients.itervalues():
1540
2055
if c.fingerprint == fpr:
1544
logger.warning(u"Client not found for fingerprint: %s, ad"
1545
u"dress: %s", fpr, address)
2059
logger.info("Client not found for fingerprint: %s, ad"
2060
"dress: %s", fpr, address)
1546
2061
if self.use_dbus:
1547
2062
# Emit D-Bus signal
1548
mandos_dbus_service.ClientNotFound(fpr, address[0])
2063
mandos_dbus_service.ClientNotFound(fpr,
1549
2065
parent_pipe.send(False)
1552
2068
gobject.io_add_watch(parent_pipe.fileno(),
1553
2069
gobject.IO_IN | gobject.IO_HUP,
1554
2070
functools.partial(self.handle_ipc,
1555
parent_pipe = parent_pipe,
1556
client_object = client))
1557
2076
parent_pipe.send(True)
1558
# remove the old hook in favor of the new above hook on same fileno
2077
# remove the old hook in favor of the new above hook on
1560
2080
if command == 'funcall':
1561
2081
funcname = request[1]
1562
2082
args = request[2]
1563
2083
kwargs = request[3]
1565
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
2085
parent_pipe.send(('data', getattr(client_object,
1567
2089
if command == 'getattr':
1568
2090
attrname = request[1]
1569
2091
if callable(client_object.__getattribute__(attrname)):
1570
2092
parent_pipe.send(('function',))
1572
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
2094
parent_pipe.send(('data', client_object
2095
.__getattribute__(attrname)))
1574
2097
if command == 'setattr':
1575
2098
attrname = request[1]
1576
2099
value = request[2]
1577
2100
setattr(client_object, attrname, value)
2105
def rfc3339_duration_to_delta(duration):
2106
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2108
>>> rfc3339_duration_to_delta("P7D")
2109
datetime.timedelta(7)
2110
>>> rfc3339_duration_to_delta("PT60S")
2111
datetime.timedelta(0, 60)
2112
>>> rfc3339_duration_to_delta("PT60M")
2113
datetime.timedelta(0, 3600)
2114
>>> rfc3339_duration_to_delta("PT24H")
2115
datetime.timedelta(1)
2116
>>> rfc3339_duration_to_delta("P1W")
2117
datetime.timedelta(7)
2118
>>> rfc3339_duration_to_delta("PT5M30S")
2119
datetime.timedelta(0, 330)
2120
>>> rfc3339_duration_to_delta("P1DT3M20S")
2121
datetime.timedelta(1, 200)
2124
# Parsing an RFC 3339 duration with regular expressions is not
2125
# possible - there would have to be multiple places for the same
2126
# values, like seconds. The current code, while more esoteric, is
2127
# cleaner without depending on a parsing library. If Python had a
2128
# built-in library for parsing we would use it, but we'd like to
2129
# avoid excessive use of external libraries.
2131
# New type for defining tokens, syntax, and semantics all-in-one
2132
Token = collections.namedtuple("Token",
2133
("regexp", # To match token; if
2134
# "value" is not None,
2135
# must have a "group"
2137
"value", # datetime.timedelta or
2139
"followers")) # Tokens valid after
2141
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2142
# the "duration" ABNF definition in RFC 3339, Appendix A.
2143
token_end = Token(re.compile(r"$"), None, frozenset())
2144
token_second = Token(re.compile(r"(\d+)S"),
2145
datetime.timedelta(seconds=1),
2146
frozenset((token_end,)))
2147
token_minute = Token(re.compile(r"(\d+)M"),
2148
datetime.timedelta(minutes=1),
2149
frozenset((token_second, token_end)))
2150
token_hour = Token(re.compile(r"(\d+)H"),
2151
datetime.timedelta(hours=1),
2152
frozenset((token_minute, token_end)))
2153
token_time = Token(re.compile(r"T"),
2155
frozenset((token_hour, token_minute,
2157
token_day = Token(re.compile(r"(\d+)D"),
2158
datetime.timedelta(days=1),
2159
frozenset((token_time, token_end)))
2160
token_month = Token(re.compile(r"(\d+)M"),
2161
datetime.timedelta(weeks=4),
2162
frozenset((token_day, token_end)))
2163
token_year = Token(re.compile(r"(\d+)Y"),
2164
datetime.timedelta(weeks=52),
2165
frozenset((token_month, token_end)))
2166
token_week = Token(re.compile(r"(\d+)W"),
2167
datetime.timedelta(weeks=1),
2168
frozenset((token_end,)))
2169
token_duration = Token(re.compile(r"P"), None,
2170
frozenset((token_year, token_month,
2171
token_day, token_time,
2173
# Define starting values
2174
value = datetime.timedelta() # Value so far
2176
followers = frozenset((token_duration,)) # Following valid tokens
2177
s = duration # String left to parse
2178
# Loop until end token is found
2179
while found_token is not token_end:
2180
# Search for any currently valid tokens
2181
for token in followers:
2182
match = token.regexp.match(s)
2183
if match is not None:
2185
if token.value is not None:
2186
# Value found, parse digits
2187
factor = int(match.group(1), 10)
2188
# Add to value so far
2189
value += factor * token.value
2190
# Strip token from string
2191
s = token.regexp.sub("", s, 1)
2194
# Set valid next tokens
2195
followers = found_token.followers
2198
# No currently valid tokens were found
2199
raise ValueError("Invalid RFC 3339 duration")
1582
2204
def string_to_delta(interval):
1583
2205
"""Parse a string and return a datetime.timedelta
1585
>>> string_to_delta(u'7d')
2207
>>> string_to_delta('7d')
1586
2208
datetime.timedelta(7)
1587
>>> string_to_delta(u'60s')
2209
>>> string_to_delta('60s')
1588
2210
datetime.timedelta(0, 60)
1589
>>> string_to_delta(u'60m')
2211
>>> string_to_delta('60m')
1590
2212
datetime.timedelta(0, 3600)
1591
>>> string_to_delta(u'24h')
2213
>>> string_to_delta('24h')
1592
2214
datetime.timedelta(1)
1593
>>> string_to_delta(u'1w')
2215
>>> string_to_delta('1w')
1594
2216
datetime.timedelta(7)
1595
>>> string_to_delta(u'5m 30s')
2217
>>> string_to_delta('5m 30s')
1596
2218
datetime.timedelta(0, 330)
2222
return rfc3339_duration_to_delta(interval)
1598
2226
timevalue = datetime.timedelta(0)
1599
2227
for s in interval.split():
1601
2229
suffix = unicode(s[-1])
1602
2230
value = int(s[:-1])
1604
2232
delta = datetime.timedelta(value)
1605
elif suffix == u"s":
1606
2234
delta = datetime.timedelta(0, value)
1607
elif suffix == u"m":
1608
2236
delta = datetime.timedelta(0, 0, 0, 0, value)
1609
elif suffix == u"h":
1610
2238
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1611
elif suffix == u"w":
1612
2240
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1614
raise ValueError(u"Unknown suffix %r" % suffix)
1615
except (ValueError, IndexError), e:
1616
raise ValueError(e.message)
2242
raise ValueError("Unknown suffix {!r}"
2244
except IndexError as e:
2245
raise ValueError(*(e.args))
1617
2246
timevalue += delta
1618
2247
return timevalue
1621
def if_nametoindex(interface):
1622
"""Call the C function if_nametoindex(), or equivalent
1624
Note: This function cannot accept a unicode string."""
1625
global if_nametoindex
1627
if_nametoindex = (ctypes.cdll.LoadLibrary
1628
(ctypes.util.find_library(u"c"))
1630
except (OSError, AttributeError):
1631
logger.warning(u"Doing if_nametoindex the hard way")
1632
def if_nametoindex(interface):
1633
"Get an interface index the hard way, i.e. using fcntl()"
1634
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1635
with contextlib.closing(socket.socket()) as s:
1636
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1637
struct.pack(str(u"16s16x"),
1639
interface_index = struct.unpack(str(u"I"),
1641
return interface_index
1642
return if_nametoindex(interface)
1645
2250
def daemon(nochdir = False, noclose = False):
1646
2251
"""See daemon(3). Standard BSD Unix function.
1672
2276
##################################################################
1673
2277
# Parsing of options, both command line and config file
1675
parser = optparse.OptionParser(version = "%%prog %s" % version)
1676
parser.add_option("-i", u"--interface", type=u"string",
1677
metavar="IF", help=u"Bind to interface IF")
1678
parser.add_option("-a", u"--address", type=u"string",
1679
help=u"Address to listen for requests on")
1680
parser.add_option("-p", u"--port", type=u"int",
1681
help=u"Port number to receive requests on")
1682
parser.add_option("--check", action=u"store_true",
1683
help=u"Run self-test")
1684
parser.add_option("--debug", action=u"store_true",
1685
help=u"Debug mode; run in foreground and log to"
1687
parser.add_option("--debuglevel", type=u"string", metavar="LEVEL",
1688
help=u"Debug level for stdout output")
1689
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1690
u" priority string (see GnuTLS documentation)")
1691
parser.add_option("--servicename", type=u"string",
1692
metavar=u"NAME", help=u"Zeroconf service name")
1693
parser.add_option("--configdir", type=u"string",
1694
default=u"/etc/mandos", metavar=u"DIR",
1695
help=u"Directory to search for configuration"
1697
parser.add_option("--no-dbus", action=u"store_false",
1698
dest=u"use_dbus", help=u"Do not provide D-Bus"
1699
u" system bus interface")
1700
parser.add_option("--no-ipv6", action=u"store_false",
1701
dest=u"use_ipv6", help=u"Do not use IPv6")
1702
options = parser.parse_args()[0]
2279
parser = argparse.ArgumentParser()
2280
parser.add_argument("-v", "--version", action="version",
2281
version = "%(prog)s {}".format(version),
2282
help="show version number and exit")
2283
parser.add_argument("-i", "--interface", metavar="IF",
2284
help="Bind to interface IF")
2285
parser.add_argument("-a", "--address",
2286
help="Address to listen for requests on")
2287
parser.add_argument("-p", "--port", type=int,
2288
help="Port number to receive requests on")
2289
parser.add_argument("--check", action="store_true",
2290
help="Run self-test")
2291
parser.add_argument("--debug", action="store_true",
2292
help="Debug mode; run in foreground and log"
2293
" to terminal", default=None)
2294
parser.add_argument("--debuglevel", metavar="LEVEL",
2295
help="Debug level for stdout output")
2296
parser.add_argument("--priority", help="GnuTLS"
2297
" priority string (see GnuTLS documentation)")
2298
parser.add_argument("--servicename",
2299
metavar="NAME", help="Zeroconf service name")
2300
parser.add_argument("--configdir",
2301
default="/etc/mandos", metavar="DIR",
2302
help="Directory to search for configuration"
2304
parser.add_argument("--no-dbus", action="store_false",
2305
dest="use_dbus", help="Do not provide D-Bus"
2306
" system bus interface", default=None)
2307
parser.add_argument("--no-ipv6", action="store_false",
2308
dest="use_ipv6", help="Do not use IPv6",
2310
parser.add_argument("--no-restore", action="store_false",
2311
dest="restore", help="Do not restore stored"
2312
" state", default=None)
2313
parser.add_argument("--socket", type=int,
2314
help="Specify a file descriptor to a network"
2315
" socket to use instead of creating one")
2316
parser.add_argument("--statedir", metavar="DIR",
2317
help="Directory to save/restore state in")
2318
parser.add_argument("--foreground", action="store_true",
2319
help="Run in foreground", default=None)
2320
parser.add_argument("--no-zeroconf", action="store_false",
2321
dest="zeroconf", help="Do not use Zeroconf",
2324
options = parser.parse_args()
1704
2326
if options.check:
2328
fail_count, test_count = doctest.testmod()
2329
sys.exit(os.EX_OK if fail_count == 0 else 1)
1709
2331
# Default values for config file for server-global settings
1710
server_defaults = { u"interface": u"",
1715
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1716
u"servicename": u"Mandos",
1717
u"use_dbus": u"True",
1718
u"use_ipv6": u"True",
2332
server_defaults = { "interface": "",
2337
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2338
"servicename": "Mandos",
2344
"statedir": "/var/lib/mandos",
2345
"foreground": "False",
1722
2349
# Parse config file for server-global settings
1723
2350
server_config = configparser.SafeConfigParser(server_defaults)
1724
2351
del server_defaults
1725
2352
server_config.read(os.path.join(options.configdir,
1727
2354
# Convert the SafeConfigParser object to a dict
1728
2355
server_settings = server_config.defaults()
1729
2356
# Use the appropriate methods on the non-string config options
1730
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1731
server_settings[option] = server_config.getboolean(u"DEFAULT",
2357
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2358
server_settings[option] = server_config.getboolean("DEFAULT",
1733
2360
if server_settings["port"]:
1734
server_settings["port"] = server_config.getint(u"DEFAULT",
2361
server_settings["port"] = server_config.getint("DEFAULT",
2363
if server_settings["socket"]:
2364
server_settings["socket"] = server_config.getint("DEFAULT",
2366
# Later, stdin will, and stdout and stderr might, be dup'ed
2367
# over with an opened os.devnull. But we don't want this to
2368
# happen with a supplied network socket.
2369
if 0 <= server_settings["socket"] <= 2:
2370
server_settings["socket"] = os.dup(server_settings
1736
2372
del server_config
1738
2374
# Override the settings from the config file with command line
1739
2375
# options, if set.
1740
for option in (u"interface", u"address", u"port", u"debug",
1741
u"priority", u"servicename", u"configdir",
1742
u"use_dbus", u"use_ipv6", u"debuglevel"):
2376
for option in ("interface", "address", "port", "debug",
2377
"priority", "servicename", "configdir",
2378
"use_dbus", "use_ipv6", "debuglevel", "restore",
2379
"statedir", "socket", "foreground", "zeroconf"):
1743
2380
value = getattr(options, option)
1744
2381
if value is not None:
1745
2382
server_settings[option] = value
1748
2385
for option in server_settings.keys():
1749
2386
if type(server_settings[option]) is str:
1750
2387
server_settings[option] = unicode(server_settings[option])
2388
# Force all boolean options to be boolean
2389
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2390
"foreground", "zeroconf"):
2391
server_settings[option] = bool(server_settings[option])
2392
# Debug implies foreground
2393
if server_settings["debug"]:
2394
server_settings["foreground"] = True
1751
2395
# Now we have our good server settings in "server_settings"
1753
2397
##################################################################
2399
if (not server_settings["zeroconf"] and
2400
not (server_settings["port"]
2401
or server_settings["socket"] != "")):
2402
parser.error("Needs port or socket to work without"
1755
2405
# For convenience
1756
debug = server_settings[u"debug"]
1757
debuglevel = server_settings[u"debuglevel"]
1758
use_dbus = server_settings[u"use_dbus"]
1759
use_ipv6 = server_settings[u"use_ipv6"]
1761
if server_settings[u"servicename"] != u"Mandos":
2406
debug = server_settings["debug"]
2407
debuglevel = server_settings["debuglevel"]
2408
use_dbus = server_settings["use_dbus"]
2409
use_ipv6 = server_settings["use_ipv6"]
2410
stored_state_path = os.path.join(server_settings["statedir"],
2412
foreground = server_settings["foreground"]
2413
zeroconf = server_settings["zeroconf"]
2416
initlogger(debug, logging.DEBUG)
2421
level = getattr(logging, debuglevel.upper())
2422
initlogger(debug, level)
2424
if server_settings["servicename"] != "Mandos":
1762
2425
syslogger.setFormatter(logging.Formatter
1763
(u'Mandos (%s) [%%(process)d]:'
1764
u' %%(levelname)s: %%(message)s'
1765
% server_settings[u"servicename"]))
2426
('Mandos ({}) [%(process)d]:'
2427
' %(levelname)s: %(message)s'
2428
.format(server_settings
1767
2431
# Parse config file with clients
1768
client_defaults = { u"timeout": u"1h",
1770
u"checker": u"fping -q -- %%(host)s",
1772
u"approval_delay": u"0s",
1773
u"approval_duration": u"1s",
1775
client_config = configparser.SafeConfigParser(client_defaults)
1776
client_config.read(os.path.join(server_settings[u"configdir"],
2432
client_config = configparser.SafeConfigParser(Client
2434
client_config.read(os.path.join(server_settings["configdir"],
1779
2437
global mandos_dbus_service
1780
2438
mandos_dbus_service = None
1782
tcp_server = MandosServer((server_settings[u"address"],
1783
server_settings[u"port"]),
2441
if server_settings["socket"] != "":
2442
socketfd = server_settings["socket"]
2443
tcp_server = MandosServer((server_settings["address"],
2444
server_settings["port"]),
1785
interface=(server_settings[u"interface"]
2446
interface=(server_settings["interface"]
1787
2448
use_ipv6=use_ipv6,
1788
2449
gnutls_priority=
1789
server_settings[u"priority"],
1792
pidfilename = u"/var/run/mandos.pid"
2450
server_settings["priority"],
2454
pidfilename = "/run/mandos.pid"
2455
if not os.path.isdir("/run/."):
2456
pidfilename = "/var/run/mandos.pid"
1794
pidfile = open(pidfilename, u"w")
1796
logger.error(u"Could not open file %r", pidfilename)
2459
pidfile = open(pidfilename, "w")
2460
except IOError as e:
2461
logger.error("Could not open file %r", pidfilename,
1799
uid = pwd.getpwnam(u"_mandos").pw_uid
1800
gid = pwd.getpwnam(u"_mandos").pw_gid
2464
for name in ("_mandos", "mandos", "nobody"):
1803
uid = pwd.getpwnam(u"mandos").pw_uid
1804
gid = pwd.getpwnam(u"mandos").pw_gid
2466
uid = pwd.getpwnam(name).pw_uid
2467
gid = pwd.getpwnam(name).pw_gid
1805
2469
except KeyError:
1807
uid = pwd.getpwnam(u"nobody").pw_uid
1808
gid = pwd.getpwnam(u"nobody").pw_gid
1815
except OSError, error:
1816
if error[0] != errno.EPERM:
2477
except OSError as error:
2478
if error.errno != errno.EPERM:
1819
if not debug and not debuglevel:
1820
syslogger.setLevel(logging.WARNING)
1821
console.setLevel(logging.WARNING)
1823
level = getattr(logging, debuglevel.upper())
1824
syslogger.setLevel(level)
1825
console.setLevel(level)
1828
2482
# Enable all possible GnuTLS debugging
1881
2541
client_class = Client
1883
2543
client_class = functools.partial(ClientDBus, bus = bus)
1884
def client_config_items(config, section):
1885
special_settings = {
1886
"approved_by_default":
1887
lambda: config.getboolean(section,
1888
"approved_by_default"),
1890
for name, value in config.items(section):
2545
client_settings = Client.config_parser(client_config)
2546
old_client_settings = {}
2549
# This is used to redirect stdout and stderr for checker processes
2551
wnull = open(os.devnull, "w") # A writable /dev/null
2552
# Only used if server is running in foreground but not in debug
2554
if debug or not foreground:
2557
# Get client data and settings from last running state.
2558
if server_settings["restore"]:
2560
with open(stored_state_path, "rb") as stored_state:
2561
clients_data, old_client_settings = (pickle.load
2563
os.remove(stored_state_path)
2564
except IOError as e:
2565
if e.errno == errno.ENOENT:
2566
logger.warning("Could not load persistent state: {}"
2567
.format(os.strerror(e.errno)))
2569
logger.critical("Could not load persistent state:",
2572
except EOFError as e:
2573
logger.warning("Could not load persistent state: "
2574
"EOFError:", exc_info=e)
2576
with PGPEngine() as pgp:
2577
for client_name, client in clients_data.items():
2578
# Skip removed clients
2579
if client_name not in client_settings:
2582
# Decide which value to use after restoring saved state.
2583
# We have three different values: Old config file,
2584
# new config file, and saved state.
2585
# New config value takes precedence if it differs from old
2586
# config value, otherwise use saved state.
2587
for name, value in client_settings[client_name].items():
2589
# For each value in new config, check if it
2590
# differs from the old config value (Except for
2591
# the "secret" attribute)
2592
if (name != "secret" and
2593
value != old_client_settings[client_name]
2595
client[name] = value
2599
# Clients who has passed its expire date can still be
2600
# enabled if its last checker was successful. Clients
2601
# whose checker succeeded before we stored its state is
2602
# assumed to have successfully run all checkers during
2604
if client["enabled"]:
2605
if datetime.datetime.utcnow() >= client["expires"]:
2606
if not client["last_checked_ok"]:
2608
"disabling client {} - Client never "
2609
"performed a successful checker"
2610
.format(client_name))
2611
client["enabled"] = False
2612
elif client["last_checker_status"] != 0:
2614
"disabling client {} - Client last"
2615
" checker failed with error code {}"
2616
.format(client_name,
2617
client["last_checker_status"]))
2618
client["enabled"] = False
2620
client["expires"] = (datetime.datetime
2622
+ client["timeout"])
2623
logger.debug("Last checker succeeded,"
2624
" keeping {} enabled"
2625
.format(client_name))
1892
yield (name, special_settings[name]())
1896
tcp_server.clients.update(set(
1897
client_class(name = section,
1898
config= dict(client_config_items(
1899
client_config, section)))
1900
for section in client_config.sections()))
2627
client["secret"] = (
2628
pgp.decrypt(client["encrypted_secret"],
2629
client_settings[client_name]
2632
# If decryption fails, we use secret from new settings
2633
logger.debug("Failed to decrypt {} old secret"
2634
.format(client_name))
2635
client["secret"] = (
2636
client_settings[client_name]["secret"])
2638
# Add/remove clients based on new changes made to config
2639
for client_name in (set(old_client_settings)
2640
- set(client_settings)):
2641
del clients_data[client_name]
2642
for client_name in (set(client_settings)
2643
- set(old_client_settings)):
2644
clients_data[client_name] = client_settings[client_name]
2646
# Create all client objects
2647
for client_name, client in clients_data.items():
2648
tcp_server.clients[client_name] = client_class(
2649
name = client_name, settings = client,
2650
server_settings = server_settings)
1901
2652
if not tcp_server.clients:
1902
logger.warning(u"No clients defined")
1908
pidfile.write(str(pid) + "\n")
1911
logger.error(u"Could not write to file %r with PID %d",
1914
# "pidfile" was never created
2653
logger.warning("No clients defined")
2656
if pidfile is not None:
2660
pidfile.write(str(pid) + "\n".encode("utf-8"))
2662
logger.error("Could not write to file %r with PID %d",
1916
2665
del pidfilename
1918
signal.signal(signal.SIGINT, signal.SIG_IGN)
1920
2667
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1921
2668
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1924
class MandosDBusService(dbus.service.Object):
2671
@alternate_dbus_interfaces({"se.recompile.Mandos":
2672
"se.bsnet.fukt.Mandos"})
2673
class MandosDBusService(DBusObjectWithProperties):
1925
2674
"""A D-Bus proxy object"""
1926
2675
def __init__(self):
1927
dbus.service.Object.__init__(self, bus, u"/")
1928
_interface = u"se.bsnet.fukt.Mandos"
1930
@dbus.service.signal(_interface, signature=u"o")
2676
dbus.service.Object.__init__(self, bus, "/")
2677
_interface = "se.recompile.Mandos"
2679
@dbus_interface_annotations(_interface)
2681
return { "org.freedesktop.DBus.Property"
2682
".EmitsChangedSignal":
2685
@dbus.service.signal(_interface, signature="o")
1931
2686
def ClientAdded(self, objpath):
1935
@dbus.service.signal(_interface, signature=u"ss")
2690
@dbus.service.signal(_interface, signature="ss")
1936
2691
def ClientNotFound(self, fingerprint, address):
1940
@dbus.service.signal(_interface, signature=u"os")
2695
@dbus.service.signal(_interface, signature="os")
1941
2696
def ClientRemoved(self, objpath, name):
1945
@dbus.service.method(_interface, out_signature=u"ao")
2700
@dbus.service.method(_interface, out_signature="ao")
1946
2701
def GetAllClients(self):
1948
2703
return dbus.Array(c.dbus_object_path
1949
for c in tcp_server.clients)
2705
tcp_server.clients.itervalues())
1951
2707
@dbus.service.method(_interface,
1952
out_signature=u"a{oa{sv}}")
2708
out_signature="a{oa{sv}}")
1953
2709
def GetAllClientsWithProperties(self):
1955
2711
return dbus.Dictionary(
1956
((c.dbus_object_path, c.GetAll(u""))
1957
for c in tcp_server.clients),
1958
signature=u"oa{sv}")
2712
((c.dbus_object_path, c.GetAll(""))
2713
for c in tcp_server.clients.itervalues()),
1960
@dbus.service.method(_interface, in_signature=u"o")
2716
@dbus.service.method(_interface, in_signature="o")
1961
2717
def RemoveClient(self, object_path):
1963
for c in tcp_server.clients:
2719
for c in tcp_server.clients.itervalues():
1964
2720
if c.dbus_object_path == object_path:
1965
tcp_server.clients.remove(c)
2721
del tcp_server.clients[c.name]
1966
2722
c.remove_from_connection()
1967
2723
# Don't signal anything except ClientRemoved
1968
2724
c.disable(quiet=True)
1979
2735
"Cleanup function; run on exit"
2739
multiprocessing.active_children()
2741
if not (tcp_server.clients or client_settings):
2744
# Store client before exiting. Secrets are encrypted with key
2745
# based on what config file has. If config file is
2746
# removed/edited, old secret will thus be unrecovable.
2748
with PGPEngine() as pgp:
2749
for client in tcp_server.clients.itervalues():
2750
key = client_settings[client.name]["secret"]
2751
client.encrypted_secret = pgp.encrypt(client.secret,
2755
# A list of attributes that can not be pickled
2757
exclude = { "bus", "changedstate", "secret",
2758
"checker", "server_settings" }
2759
for name, typ in (inspect.getmembers
2760
(dbus.service.Object)):
2763
client_dict["encrypted_secret"] = (client
2765
for attr in client.client_structure:
2766
if attr not in exclude:
2767
client_dict[attr] = getattr(client, attr)
2769
clients[client.name] = client_dict
2770
del client_settings[client.name]["secret"]
2773
with (tempfile.NamedTemporaryFile
2774
(mode='wb', suffix=".pickle", prefix='clients-',
2775
dir=os.path.dirname(stored_state_path),
2776
delete=False)) as stored_state:
2777
pickle.dump((clients, client_settings), stored_state)
2778
tempname=stored_state.name
2779
os.rename(tempname, stored_state_path)
2780
except (IOError, OSError) as e:
2786
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2787
logger.warning("Could not save persistent state: {}"
2788
.format(os.strerror(e.errno)))
2790
logger.warning("Could not save persistent state:",
2794
# Delete all clients, and settings from config
1982
2795
while tcp_server.clients:
1983
client = tcp_server.clients.pop()
2796
name, client = tcp_server.clients.popitem()
1985
2798
client.remove_from_connection()
1986
client.disable_hook = None
1987
2799
# Don't signal anything except ClientRemoved
1988
2800
client.disable(quiet=True)
1990
2802
# Emit D-Bus signal
1991
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2803
mandos_dbus_service.ClientRemoved(client
2806
client_settings.clear()
1994
2808
atexit.register(cleanup)
1996
for client in tcp_server.clients:
2810
for client in tcp_server.clients.itervalues():
1998
2812
# Emit D-Bus signal
1999
2813
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2814
# Need to initiate checking of clients
2816
client.init_checker()
2002
2818
tcp_server.enable()
2003
2819
tcp_server.server_activate()
2005
2821
# Find out what port we got
2006
service.port = tcp_server.socket.getsockname()[1]
2823
service.port = tcp_server.socket.getsockname()[1]
2008
logger.info(u"Now listening on address %r, port %d,"
2009
" flowinfo %d, scope_id %d"
2010
% tcp_server.socket.getsockname())
2825
logger.info("Now listening on address %r, port %d,"
2826
" flowinfo %d, scope_id %d",
2827
*tcp_server.socket.getsockname())
2012
logger.info(u"Now listening on address %r, port %d"
2013
% tcp_server.socket.getsockname())
2829
logger.info("Now listening on address %r, port %d",
2830
*tcp_server.socket.getsockname())
2015
2832
#service.interface = tcp_server.socket.getsockname()[3]
2018
# From the Avahi example code
2021
except dbus.exceptions.DBusException, error:
2022
logger.critical(u"DBusException: %s", error)
2025
# End of Avahi example code
2836
# From the Avahi example code
2839
except dbus.exceptions.DBusException as error:
2840
logger.critical("D-Bus Exception", exc_info=error)
2843
# End of Avahi example code
2027
2845
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2028
2846
lambda *args, **kwargs:
2029
2847
(tcp_server.handle_request
2030
2848
(*args[2:], **kwargs) or True))
2032
logger.debug(u"Starting main loop")
2850
logger.debug("Starting main loop")
2033
2851
main_loop.run()
2034
except AvahiError, error:
2035
logger.critical(u"AvahiError: %s", error)
2852
except AvahiError as error:
2853
logger.critical("Avahi Error", exc_info=error)
2038
2856
except KeyboardInterrupt:
2041
logger.debug(u"Server received KeyboardInterrupt")
2042
logger.debug(u"Server exiting")
2858
print("", file=sys.stderr)
2859
logger.debug("Server received KeyboardInterrupt")
2860
logger.debug("Server exiting")
2043
2861
# Must run before the D-Bus bus name gets deregistered