88
80
except ImportError:
89
81
SO_BINDTODEVICE = None
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
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)
224
102
class AvahiError(Exception):
225
103
def __init__(self, value, *args, **kwargs):
325
197
dbus.UInt16(self.port),
326
198
avahi.string_array_to_txt_array(self.TXT))
327
199
self.group.Commit()
329
200
def entry_group_state_changed(self, state, error):
330
201
"""Derived from the Avahi example code"""
331
logger.debug("Avahi entry group state change: %i", state)
202
logger.debug(u"Avahi entry group state change: %i", state)
333
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
334
logger.debug("Zeroconf service established.")
205
logger.debug(u"Zeroconf service established.")
335
206
elif state == avahi.ENTRY_GROUP_COLLISION:
336
logger.info("Zeroconf service name collision.")
207
logger.warning(u"Zeroconf service name collision.")
338
209
elif state == avahi.ENTRY_GROUP_FAILURE:
339
logger.critical("Avahi: Error in group state changed %s",
210
logger.critical(u"Avahi: Error in group state changed %s",
341
raise AvahiGroupError("State changed: {!s}"
212
raise AvahiGroupError(u"State changed: %s"
344
214
def cleanup(self):
345
215
"""Derived from the Avahi example code"""
346
216
if self.group is not None:
349
except (dbus.exceptions.UnknownMethodException,
350
dbus.exceptions.DBusException):
352
218
self.group = None
355
def server_state_changed(self, state, error=None):
219
def server_state_changed(self, state):
356
220
"""Derived from the Avahi example code"""
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)
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
372
225
elif state == avahi.SERVER_RUNNING:
376
logger.debug("Unknown state: %r", state)
378
logger.debug("Unknown state: %r: %r", state, error)
380
227
def activate(self):
381
228
"""Derived from the Avahi example code"""
382
229
if self.server is None:
383
230
self.server = dbus.Interface(
384
231
self.bus.get_object(avahi.DBUS_NAME,
385
avahi.DBUS_PATH_SERVER,
386
follow_name_owner_changes=True),
232
avahi.DBUS_PATH_SERVER),
387
233
avahi.DBUS_INTERFACE_SERVER)
388
self.server.connect_to_signal("StateChanged",
234
self.server.connect_to_signal(u"StateChanged",
389
235
self.server_state_changed)
390
236
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'
404
239
class Client(object):
405
240
"""A representation of a client host served by this server.
408
approved: bool(); 'None' if not yet approved/disapproved
243
_approved: bool(); 'None' if not yet approved/disapproved
409
244
approval_delay: datetime.timedelta(); Time to wait for approval
410
245
approval_duration: datetime.timedelta(); Duration of one approval
411
246
checker: subprocess.Popen(); a running checker process used
429
263
interval: datetime.timedelta(); How often to start a new checker
430
264
last_approval_request: datetime.datetime(); (UTC) or None
431
265
last_checked_ok: datetime.datetime(); (UTC) or None
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
266
last_enabled: datetime.datetime(); (UTC)
436
267
name: string; from the config file, used in log messages and
437
268
D-Bus identifiers
438
269
secret: bytestring; sent verbatim (over TLS) to client
439
270
timeout: datetime.timedelta(); How long from last_checked_ok
440
271
until this client is disabled
441
extended_timeout: extra long timeout when secret has been sent
442
272
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()
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",
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")
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):
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'
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)
305
logger.debug(u"Creating client %r", self.name)
531
306
# Uppercase and remove spaces from fingerprint for later
532
307
# comparison purposes with return value from the fingerprint()
534
logger.debug(" Fingerprint: %s", self.fingerprint)
535
self.created = settings.get("created",
536
datetime.datetime.utcnow())
538
# attributes specific for this server instance
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
539
331
self.checker = None
540
332
self.checker_initiator_tag = None
541
333
self.disable_initiator_tag = None
542
334
self.checker_callback_tag = None
335
self.checker_command = config[u"checker"]
543
336
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",
545
341
self.approvals_pending = 0
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)
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())
561
# Send notice to process children that client state has changed
562
348
def send_changedstate(self):
563
with self.changedstate:
564
self.changedstate.notify_all()
349
self.changedstate.acquire()
350
self.changedstate.notify_all()
351
self.changedstate.release()
566
353
def enable(self):
567
354
"""Start this client's checker and timeout hooks"""
568
if getattr(self, "enabled", False):
355
if getattr(self, u"enabled", False):
569
356
# Already enabled
571
self.expires = datetime.datetime.utcnow() + self.timeout
358
self.send_changedstate()
573
359
self.last_enabled = datetime.datetime.utcnow()
575
self.send_changedstate()
360
# Schedule a new checker to be started an 'interval' from now,
361
# and every interval from then on.
362
self.checker_initiator_tag = (gobject.timeout_add
363
(self.interval_milliseconds(),
365
# Schedule a disable() when 'timeout' has passed
366
self.disable_initiator_tag = (gobject.timeout_add
367
(self.timeout_milliseconds(),
370
# Also start a new checker *right now*.
577
373
def disable(self, quiet=True):
578
374
"""Disable this client."""
579
375
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:
378
self.send_changedstate()
380
logger.info(u"Disabling client %s", self.name)
381
if getattr(self, u"disable_initiator_tag", False):
584
382
gobject.source_remove(self.disable_initiator_tag)
585
383
self.disable_initiator_tag = None
587
if getattr(self, "checker_initiator_tag", None) is not None:
384
if getattr(self, u"checker_initiator_tag", False):
588
385
gobject.source_remove(self.checker_initiator_tag)
589
386
self.checker_initiator_tag = None
590
387
self.stop_checker()
388
if self.disable_hook:
389
self.disable_hook(self)
591
390
self.enabled = False
593
self.send_changedstate()
594
391
# Do not run this again if called by a gobject.timeout_add
597
394
def __del__(self):
395
self.disable_hook = None
600
def init_checker(self):
601
# Schedule a new checker to be started an 'interval' from now,
602
# and every interval from then on.
603
if self.checker_initiator_tag is not None:
604
gobject.source_remove(self.checker_initiator_tag)
605
self.checker_initiator_tag = (gobject.timeout_add
607
.total_seconds() * 1000),
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)
612
self.disable_initiator_tag = (gobject.timeout_add
614
.total_seconds() * 1000),
616
# Also start a new checker *right now*.
619
398
def checker_callback(self, pid, condition, command):
620
399
"""The checker has completed, so take appropriate actions."""
621
400
self.checker_callback_tag = None
622
401
self.checker = None
623
402
if os.WIFEXITED(condition):
624
self.last_checker_status = os.WEXITSTATUS(condition)
625
if self.last_checker_status == 0:
626
logger.info("Checker for %(name)s succeeded",
403
exitstatus = os.WEXITSTATUS(condition)
405
logger.info(u"Checker for %(name)s succeeded",
628
407
self.checked_ok()
630
logger.info("Checker for %(name)s failed",
409
logger.info(u"Checker for %(name)s failed",
633
self.last_checker_status = -1
634
logger.warning("Checker for %(name)s crashed?",
412
logger.warning(u"Checker for %(name)s crashed?",
637
415
def checked_ok(self):
638
"""Assert that the client has been seen, alive and well."""
416
"""Bump up the timeout for this client.
418
This should only be called when the client has been seen,
639
421
self.last_checked_ok = datetime.datetime.utcnow()
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
422
gobject.source_remove(self.disable_initiator_tag)
423
self.disable_initiator_tag = (gobject.timeout_add
424
(self.timeout_milliseconds(),
656
427
def need_approval(self):
657
428
self.last_approval_request = datetime.datetime.utcnow()
673
444
# If a checker exists, make sure it is not a zombie
675
446
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
676
except AttributeError:
678
except OSError as error:
679
if error.errno != errno.ECHILD:
447
except (AttributeError, OSError), error:
448
if (isinstance(error, OSError)
449
and error.errno != errno.ECHILD):
683
logger.warning("Checker was a zombie")
453
logger.warning(u"Checker was a zombie")
684
454
gobject.source_remove(self.checker_callback_tag)
685
455
self.checker_callback(pid, status,
686
456
self.current_checker_command)
687
457
# Start a new checker if needed
688
458
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 }
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
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
700
478
self.current_checker_command = command
702
logger.info("Starting checker %r for %s",
480
logger.info(u"Starting checker %r for %s",
703
481
command, self.name)
704
482
# We don't need to redirect stdout and stderr, since
705
483
# in normal mode, that is already done by daemon(),
706
484
# and in debug mode we don't want to. (Stdin is
707
485
# 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,
716
486
self.checker = subprocess.Popen(command,
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.
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.
731
495
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
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)
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",
742
502
# Re-run this periodically if run by gobject.timeout_add
850
572
class DBusObjectWithProperties(dbus.service.Object):
851
573
"""A D-Bus object with properties.
853
575
Classes inheriting from this can use the dbus_service_property
854
576
decorator to expose methods as D-Bus properties. It exposes the
855
577
standard Get(), Set(), and GetAll() methods on the D-Bus.
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),
581
def _is_dbus_property(obj):
582
return getattr(obj, u"_dbus_is_property", False)
868
def _get_all_dbus_things(self, thing):
584
def _get_all_dbus_properties(self):
869
585
"""Returns a generator of (name, attribute) pairs
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)))
587
return ((prop._dbus_name, prop)
589
inspect.getmembers(self, self._is_dbus_property))
879
591
def _get_dbus_property(self, interface_name, property_name):
880
592
"""Returns a bound method if one exists which is a D-Bus
881
593
property with the specified name and 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)
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)):
891
605
# No such property
892
raise DBusPropertyNotFound(self.dbus_object_path + ":"
893
+ interface_name + "."
606
raise DBusPropertyNotFound(self.dbus_object_path + u":"
607
+ interface_name + u"."
896
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
898
612
def Get(self, interface_name, property_name):
899
613
"""Standard D-Bus property Get() method, see D-Bus standard.
901
615
prop = self._get_dbus_property(interface_name, property_name)
902
if prop._dbus_access == "write":
616
if prop._dbus_access == u"write":
903
617
raise DBusPropertyAccessException(property_name)
905
if not hasattr(value, "variant_level"):
619
if not hasattr(value, u"variant_level"):
907
621
return type(value)(value, variant_level=value.variant_level+1)
909
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
623
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
910
624
def Set(self, interface_name, property_name, value):
911
625
"""Standard D-Bus property Set() method, see D-Bus standard.
913
627
prop = self._get_dbus_property(interface_name, property_name)
914
if prop._dbus_access == "read":
628
if prop._dbus_access == u"read":
915
629
raise DBusPropertyAccessException(property_name)
916
if prop._dbus_get_args_options["byte_arrays"]:
630
if prop._dbus_get_args_options[u"byte_arrays"]:
917
631
# The byte_arrays option is not supported yet on
918
632
# signatures other than "ay".
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)
633
if prop._dbus_signature != u"ay":
635
value = dbus.ByteArray(''.join(unichr(byte)
927
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
928
out_signature="a{sv}")
639
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
640
out_signature=u"a{sv}")
929
641
def GetAll(self, interface_name):
930
642
"""Standard D-Bus property GetAll() method, see D-Bus
933
645
Note: Will not include properties with access="write".
936
for name, prop in self._get_all_dbus_things("property"):
648
for name, prop in self._get_all_dbus_properties():
937
649
if (interface_name
938
650
and interface_name != prop._dbus_interface):
939
651
# Interface non-empty but did not match
941
653
# Ignore write-only properties
942
if prop._dbus_access == "write":
654
if prop._dbus_access == u"write":
945
if not hasattr(value, "variant_level"):
946
properties[name] = value
657
if not hasattr(value, u"variant_level"):
948
properties[name] = type(value)(value, variant_level=
949
value.variant_level+1)
950
return dbus.Dictionary(properties, signature="sv")
660
all[name] = type(value)(value, variant_level=
661
value.variant_level+1)
662
return dbus.Dictionary(all, signature=u"sv")
952
664
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
954
666
path_keyword='object_path',
955
667
connection_keyword='connection')
956
668
def Introspect(self, object_path, connection):
957
"""Overloading of standard D-Bus method.
959
Inserts property tags and interface annotation tags.
669
"""Standard D-Bus method, overloaded to insert property tags.
961
671
xmlstring = dbus.service.Object.Introspect(self, object_path,
964
674
document = xml.dom.minidom.parseString(xmlstring)
965
675
def make_tag(document, name, prop):
966
e = document.createElement("property")
967
e.setAttribute("name", name)
968
e.setAttribute("type", prop._dbus_signature)
969
e.setAttribute("access", prop._dbus_access)
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)
971
for if_tag in document.getElementsByTagName("interface"):
681
for if_tag in document.getElementsByTagName(u"interface"):
973
682
for tag in (make_tag(document, name, prop)
975
in self._get_all_dbus_things("property")
684
in self._get_all_dbus_properties()
976
685
if prop._dbus_interface
977
== if_tag.getAttribute("name")):
686
== if_tag.getAttribute(u"name")):
978
687
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)
1010
688
# Add the names to the return values for the
1011
689
# "org.freedesktop.DBus.Properties" methods
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")
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")
1026
704
document.unlink()
1027
705
except (AttributeError, xml.dom.DOMException,
1028
xml.parsers.expat.ExpatError) as error:
1029
logger.error("Failed to override Introspection method",
706
xml.parsers.expat.ExpatError), error:
707
logger.error(u"Failed to override Introspection method",
1031
709
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"})
1213
712
class ClientDBus(Client, DBusObjectWithProperties):
1214
713
"""A Client class using D-Bus
1221
720
runtime_expansions = (Client.runtime_expansions
1222
+ ("dbus_object_path",))
721
+ (u"dbus_object_path",))
1224
723
# dbus.service.Object doesn't use super(), so we can't either.
1226
725
def __init__(self, bus = None, *args, **kwargs):
726
self._approvals_pending = 0
1228
728
Client.__init__(self, *args, **kwargs)
1229
729
# Only now, when this client is initialized, can it show up on
1231
731
client_object_name = unicode(self.name).translate(
1232
{ord("."): ord("_"),
1233
ord("-"): ord("_")})
732
{ord(u"."): ord(u"_"),
733
ord(u"-"): ord(u"_")})
1234
734
self.dbus_object_path = (dbus.ObjectPath
1235
("/clients/" + client_object_name))
735
(u"/clients/" + client_object_name))
1236
736
DBusObjectWithProperties.__init__(self, self.bus,
1237
737
self.dbus_object_path)
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
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))
1309
783
def __del__(self, *args, **kwargs):
1311
785
self.remove_from_connection()
1312
786
except LookupError:
1314
if hasattr(DBusObjectWithProperties, "__del__"):
788
if hasattr(DBusObjectWithProperties, u"__del__"):
1315
789
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1316
790
Client.__del__(self, *args, **kwargs)
1334
811
return Client.checker_callback(self, pid, condition, command,
1335
812
*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,
1337
832
def start_checker(self, *args, **kwargs):
1338
old_checker_pid = getattr(self.checker, "pid", None)
833
old_checker = self.checker
834
if self.checker is not None:
835
old_checker_pid = self.checker.pid
837
old_checker_pid = None
1339
838
r = Client.start_checker(self, *args, **kwargs)
1340
839
# Only if new checker process was started
1341
840
if (self.checker is not None
1342
841
and old_checker_pid != self.checker.pid):
1343
842
# Emit D-Bus signal
1344
843
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))
1347
858
def _reset_approved(self):
1348
self.approved = None
859
self._approved = None
1351
862
def approve(self, value=True):
1352
self.approved = value
1353
gobject.timeout_add(int(self.approval_duration.total_seconds()
1354
* 1000), self._reset_approved)
1355
863
self.send_changedstate()
864
self._approved = value
865
gobject.timeout_add(self._timedelta_to_milliseconds
866
(self.approval_duration),
867
self._reset_approved)
1357
870
## D-Bus methods, signals & properties
1358
_interface = "se.recompile.Mandos.Client"
1362
@dbus_interface_annotations(_interface)
1364
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
871
_interface = u"se.bsnet.fukt.Mandos.Client"
1369
875
# CheckerCompleted - signal
1370
@dbus.service.signal(_interface, signature="nxs")
876
@dbus.service.signal(_interface, signature=u"nxs")
1371
877
def CheckerCompleted(self, exitcode, waitstatus, command):
1375
881
# CheckerStarted - signal
1376
@dbus.service.signal(_interface, signature="s")
882
@dbus.service.signal(_interface, signature=u"s")
1377
883
def CheckerStarted(self, command):
1381
887
# PropertyChanged - signal
1382
@dbus.service.signal(_interface, signature="sv")
888
@dbus.service.signal(_interface, signature=u"sv")
1383
889
def PropertyChanged(self, property, value):
1445
951
# ApprovalPending - property
1446
@dbus_service_property(_interface, signature="b", access="read")
952
@dbus_service_property(_interface, signature=u"b", access=u"read")
1447
953
def ApprovalPending_dbus_property(self):
1448
954
return dbus.Boolean(bool(self.approvals_pending))
1450
956
# ApprovedByDefault - property
1451
@dbus_service_property(_interface, signature="b",
957
@dbus_service_property(_interface, signature=u"b",
1453
959
def ApprovedByDefault_dbus_property(self, value=None):
1454
960
if value is None: # get
1455
961
return dbus.Boolean(self.approved_by_default)
1456
962
self.approved_by_default = bool(value)
964
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
965
dbus.Boolean(value, variant_level=1))
1458
967
# ApprovalDelay - property
1459
@dbus_service_property(_interface, signature="t",
968
@dbus_service_property(_interface, signature=u"t",
1461
970
def ApprovalDelay_dbus_property(self, value=None):
1462
971
if value is None: # get
1463
return dbus.UInt64(self.approval_delay.total_seconds()
972
return dbus.UInt64(self.approval_delay_milliseconds())
1465
973
self.approval_delay = datetime.timedelta(0, 0, 0, value)
975
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
976
dbus.UInt64(value, variant_level=1))
1467
978
# ApprovalDuration - property
1468
@dbus_service_property(_interface, signature="t",
979
@dbus_service_property(_interface, signature=u"t",
1470
981
def ApprovalDuration_dbus_property(self, value=None):
1471
982
if value is None: # get
1472
return dbus.UInt64(self.approval_duration.total_seconds()
983
return dbus.UInt64(self._timedelta_to_milliseconds(
984
self.approval_duration))
1474
985
self.approval_duration = datetime.timedelta(0, 0, 0, value)
987
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
988
dbus.UInt64(value, variant_level=1))
1476
990
# Name - property
1477
@dbus_service_property(_interface, signature="s", access="read")
991
@dbus_service_property(_interface, signature=u"s", access=u"read")
1478
992
def Name_dbus_property(self):
1479
993
return dbus.String(self.name)
1481
995
# Fingerprint - property
1482
@dbus_service_property(_interface, signature="s", access="read")
996
@dbus_service_property(_interface, signature=u"s", access=u"read")
1483
997
def Fingerprint_dbus_property(self):
1484
998
return dbus.String(self.fingerprint)
1486
1000
# Host - property
1487
@dbus_service_property(_interface, signature="s",
1001
@dbus_service_property(_interface, signature=u"s",
1002
access=u"readwrite")
1489
1003
def Host_dbus_property(self, value=None):
1490
1004
if value is None: # get
1491
1005
return dbus.String(self.host)
1492
self.host = unicode(value)
1008
self.PropertyChanged(dbus.String(u"Host"),
1009
dbus.String(value, variant_level=1))
1494
1011
# Created - property
1495
@dbus_service_property(_interface, signature="s", access="read")
1012
@dbus_service_property(_interface, signature=u"s", access=u"read")
1496
1013
def Created_dbus_property(self):
1497
return datetime_to_dbus(self.created)
1014
return dbus.String(self._datetime_to_dbus(self.created))
1499
1016
# LastEnabled - property
1500
@dbus_service_property(_interface, signature="s", access="read")
1017
@dbus_service_property(_interface, signature=u"s", access=u"read")
1501
1018
def LastEnabled_dbus_property(self):
1502
return datetime_to_dbus(self.last_enabled)
1019
if self.last_enabled is None:
1020
return dbus.String(u"")
1021
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1504
1023
# Enabled - property
1505
@dbus_service_property(_interface, signature="b",
1024
@dbus_service_property(_interface, signature=u"b",
1025
access=u"readwrite")
1507
1026
def Enabled_dbus_property(self, value=None):
1508
1027
if value is None: # get
1509
1028
return dbus.Boolean(self.enabled)
1515
1034
# LastCheckedOK - property
1516
@dbus_service_property(_interface, signature="s",
1035
@dbus_service_property(_interface, signature=u"s",
1036
access=u"readwrite")
1518
1037
def LastCheckedOK_dbus_property(self, value=None):
1519
1038
if value is not None:
1520
1039
self.checked_ok()
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)
1041
if self.last_checked_ok is None:
1042
return dbus.String(u"")
1043
return dbus.String(self._datetime_to_dbus(self
1535
1046
# LastApprovalRequest - property
1536
@dbus_service_property(_interface, signature="s", access="read")
1047
@dbus_service_property(_interface, signature=u"s", access=u"read")
1537
1048
def LastApprovalRequest_dbus_property(self):
1538
return datetime_to_dbus(self.last_approval_request)
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))
1540
1055
# Timeout - property
1541
@dbus_service_property(_interface, signature="t",
1056
@dbus_service_property(_interface, signature=u"t",
1057
access=u"readwrite")
1543
1058
def Timeout_dbus_property(self, value=None):
1544
1059
if value is None: # get
1545
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1546
old_timeout = self.timeout
1060
return dbus.UInt64(self.timeout_milliseconds())
1547
1061
self.timeout = datetime.timedelta(0, 0, 0, value)
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)
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))
1574
1083
# Interval - property
1575
@dbus_service_property(_interface, signature="t",
1084
@dbus_service_property(_interface, signature=u"t",
1085
access=u"readwrite")
1577
1086
def Interval_dbus_property(self, value=None):
1578
1087
if value is None: # get
1579
return dbus.UInt64(self.interval.total_seconds() * 1000)
1088
return dbus.UInt64(self.interval_milliseconds())
1580
1089
self.interval = datetime.timedelta(0, 0, 0, value)
1581
if getattr(self, "checker_initiator_tag", None) is None:
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:
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
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
1590
1101
# Checker - property
1591
@dbus_service_property(_interface, signature="s",
1102
@dbus_service_property(_interface, signature=u"s",
1103
access=u"readwrite")
1593
1104
def Checker_dbus_property(self, value=None):
1594
1105
if value is None: # get
1595
1106
return dbus.String(self.checker_command)
1596
self.checker_command = unicode(value)
1107
self.checker_command = value
1109
self.PropertyChanged(dbus.String(u"Checker"),
1110
dbus.String(self.checker_command,
1598
1113
# CheckerRunning - property
1599
@dbus_service_property(_interface, signature="b",
1114
@dbus_service_property(_interface, signature=u"b",
1115
access=u"readwrite")
1601
1116
def CheckerRunning_dbus_property(self, value=None):
1602
1117
if value is None: # get
1603
1118
return dbus.Boolean(self.checker is not None)
1655
1170
def handle(self):
1656
1171
with contextlib.closing(self.server.child_pipe) as child_pipe:
1657
logger.info("TCP connection from: %s",
1172
logger.info(u"TCP connection from: %s",
1658
1173
unicode(self.client_address))
1659
logger.debug("Pipe FD: %d",
1174
logger.debug(u"Pipe FD: %d",
1660
1175
self.server.child_pipe.fileno())
1662
1177
session = (gnutls.connection
1663
1178
.ClientSession(self.request,
1664
1179
gnutls.connection
1665
1180
.X509Credentials()))
1667
1182
# Note: gnutls.connection.X509Credentials is really a
1668
1183
# generic GnuTLS certificate credentials object so long as
1669
1184
# no X.509 keys are added to it. Therefore, we can use it
1670
1185
# here despite using OpenPGP certificates.
1672
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1673
# "+AES-256-CBC", "+SHA1",
1674
# "+COMP-NULL", "+CTYPE-OPENPGP",
1187
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1188
# u"+AES-256-CBC", u"+SHA1",
1189
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1676
1191
# Use a fallback default, since this MUST be set.
1677
1192
priority = self.server.gnutls_priority
1678
1193
if priority is None:
1194
priority = u"NORMAL"
1680
1195
(gnutls.library.functions
1681
1196
.gnutls_priority_set_direct(session._c_object,
1682
1197
priority, None))
1684
1199
# Start communication using the Mandos protocol
1685
1200
# Get protocol number
1686
1201
line = self.request.makefile().readline()
1687
logger.debug("Protocol version: %r", line)
1202
logger.debug(u"Protocol version: %r", line)
1689
1204
if int(line.strip().split()[0]) > 1:
1690
raise RuntimeError(line)
1691
except (ValueError, IndexError, RuntimeError) as error:
1692
logger.error("Unknown protocol version: %s", error)
1206
except (ValueError, IndexError, RuntimeError), error:
1207
logger.error(u"Unknown protocol version: %s", error)
1695
1210
# Start GnuTLS connection
1697
1212
session.handshake()
1698
except gnutls.errors.GNUTLSError as error:
1699
logger.warning("Handshake failed: %s", error)
1213
except gnutls.errors.GNUTLSError, error:
1214
logger.warning(u"Handshake failed: %s", error)
1700
1215
# Do not run session.bye() here: the session is not
1701
1216
# established. Just abandon the request.
1703
logger.debug("Handshake succeeded")
1218
logger.debug(u"Handshake succeeded")
1705
1220
approval_required = False
1708
1223
fpr = self.fingerprint(self.peer_certificate
1711
gnutls.errors.GNUTLSError) as error:
1712
logger.warning("Bad certificate: %s", error)
1225
except (TypeError, gnutls.errors.GNUTLSError), error:
1226
logger.warning(u"Bad certificate: %s", error)
1714
logger.debug("Fingerprint: %s", fpr)
1228
logger.debug(u"Fingerprint: %s", fpr)
1717
1231
client = ProxyClient(child_pipe, fpr,
1718
1232
self.client_address)
1911
1420
use_ipv6: Boolean; to use IPv6 or not
1913
1422
def __init__(self, server_address, RequestHandlerClass,
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().
1423
interface=None, use_ipv6=True):
1918
1424
self.interface = interface
1920
1426
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.
1947
1427
socketserver.TCPServer.__init__(self, server_address,
1948
1428
RequestHandlerClass)
1950
1429
def server_bind(self):
1951
1430
"""This overrides the normal server_bind() function
1952
1431
to bind to an interface if one was specified, and also NOT to
1953
1432
bind to an address or port if they were not specified."""
1954
1433
if self.interface is not None:
1955
1434
if SO_BINDTODEVICE is None:
1956
logger.error("SO_BINDTODEVICE does not exist;"
1957
" cannot bind to interface %s",
1435
logger.error(u"SO_BINDTODEVICE does not exist;"
1436
u" cannot bind to interface %s",
1958
1437
self.interface)
1961
1440
self.socket.setsockopt(socket.SOL_SOCKET,
1962
1441
SO_BINDTODEVICE,
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)
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",
1977
1455
# Only bind(2) the socket if we really need to.
1978
1456
if self.server_address[0] or self.server_address[1]:
1979
1457
if not self.server_address[0]:
1980
1458
if self.address_family == socket.AF_INET6:
1981
any_address = "::" # in6addr_any
1459
any_address = u"::" # in6addr_any
1983
any_address = "0.0.0.0" # INADDR_ANY
1461
any_address = socket.INADDR_ANY
1984
1462
self.server_address = (any_address,
1985
1463
self.server_address[1])
1986
1464
elif not self.server_address[1]:
2051
1536
fpr = request[1]
2052
1537
address = request[2]
2054
for c in self.clients.itervalues():
1539
for c in self.clients:
2055
1540
if c.fingerprint == fpr:
2059
logger.info("Client not found for fingerprint: %s, ad"
2060
"dress: %s", fpr, address)
1544
logger.warning(u"Client not found for fingerprint: %s, ad"
1545
u"dress: %s", fpr, address)
2061
1546
if self.use_dbus:
2062
1547
# Emit D-Bus signal
2063
mandos_dbus_service.ClientNotFound(fpr,
1548
mandos_dbus_service.ClientNotFound(fpr, address[0])
2065
1549
parent_pipe.send(False)
2068
1552
gobject.io_add_watch(parent_pipe.fileno(),
2069
1553
gobject.IO_IN | gobject.IO_HUP,
2070
1554
functools.partial(self.handle_ipc,
1555
parent_pipe = parent_pipe,
1556
client_object = client))
2076
1557
parent_pipe.send(True)
2077
# remove the old hook in favor of the new above hook on
1558
# remove the old hook in favor of the new above hook on same fileno
2080
1560
if command == 'funcall':
2081
1561
funcname = request[1]
2082
1562
args = request[2]
2083
1563
kwargs = request[3]
2085
parent_pipe.send(('data', getattr(client_object,
1565
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
2089
1567
if command == 'getattr':
2090
1568
attrname = request[1]
2091
1569
if callable(client_object.__getattribute__(attrname)):
2092
1570
parent_pipe.send(('function',))
2094
parent_pipe.send(('data', client_object
2095
.__getattribute__(attrname)))
1572
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
2097
1574
if command == 'setattr':
2098
1575
attrname = request[1]
2099
1576
value = request[2]
2100
1577
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")
2204
1582
def string_to_delta(interval):
2205
1583
"""Parse a string and return a datetime.timedelta
2207
>>> string_to_delta('7d')
1585
>>> string_to_delta(u'7d')
2208
1586
datetime.timedelta(7)
2209
>>> string_to_delta('60s')
1587
>>> string_to_delta(u'60s')
2210
1588
datetime.timedelta(0, 60)
2211
>>> string_to_delta('60m')
1589
>>> string_to_delta(u'60m')
2212
1590
datetime.timedelta(0, 3600)
2213
>>> string_to_delta('24h')
1591
>>> string_to_delta(u'24h')
2214
1592
datetime.timedelta(1)
2215
>>> string_to_delta('1w')
1593
>>> string_to_delta(u'1w')
2216
1594
datetime.timedelta(7)
2217
>>> string_to_delta('5m 30s')
1595
>>> string_to_delta(u'5m 30s')
2218
1596
datetime.timedelta(0, 330)
2222
return rfc3339_duration_to_delta(interval)
2226
1598
timevalue = datetime.timedelta(0)
2227
1599
for s in interval.split():
2229
1601
suffix = unicode(s[-1])
2230
1602
value = int(s[:-1])
2232
1604
delta = datetime.timedelta(value)
1605
elif suffix == u"s":
2234
1606
delta = datetime.timedelta(0, value)
1607
elif suffix == u"m":
2236
1608
delta = datetime.timedelta(0, 0, 0, 0, value)
1609
elif suffix == u"h":
2238
1610
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1611
elif suffix == u"w":
2240
1612
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2242
raise ValueError("Unknown suffix {!r}"
2244
except IndexError as e:
2245
raise ValueError(*(e.args))
1614
raise ValueError(u"Unknown suffix %r" % suffix)
1615
except (ValueError, IndexError), e:
1616
raise ValueError(e.message)
2246
1617
timevalue += delta
2247
1618
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)
2250
1645
def daemon(nochdir = False, noclose = False):
2251
1646
"""See daemon(3). Standard BSD Unix function.
2276
1672
##################################################################
2277
1673
# Parsing of options, both command line and config file
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()
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]
2326
1704
if options.check:
2328
fail_count, test_count = doctest.testmod()
2329
sys.exit(os.EX_OK if fail_count == 0 else 1)
2331
1709
# Default values for config file for server-global settings
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",
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",
2349
1722
# Parse config file for server-global settings
2350
1723
server_config = configparser.SafeConfigParser(server_defaults)
2351
1724
del server_defaults
2352
1725
server_config.read(os.path.join(options.configdir,
2354
1727
# Convert the SafeConfigParser object to a dict
2355
1728
server_settings = server_config.defaults()
2356
1729
# Use the appropriate methods on the non-string config options
2357
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2358
server_settings[option] = server_config.getboolean("DEFAULT",
1730
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1731
server_settings[option] = server_config.getboolean(u"DEFAULT",
2360
1733
if server_settings["port"]:
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
1734
server_settings["port"] = server_config.getint(u"DEFAULT",
2372
1736
del server_config
2374
1738
# Override the settings from the config file with command line
2375
1739
# options, if set.
2376
for option in ("interface", "address", "port", "debug",
2377
"priority", "servicename", "configdir",
2378
"use_dbus", "use_ipv6", "debuglevel", "restore",
2379
"statedir", "socket", "foreground", "zeroconf"):
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"):
2380
1743
value = getattr(options, option)
2381
1744
if value is not None:
2382
1745
server_settings[option] = value
2385
1748
for option in server_settings.keys():
2386
1749
if type(server_settings[option]) is str:
2387
1750
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
2395
1751
# Now we have our good server settings in "server_settings"
2397
1753
##################################################################
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"
2405
1755
# For convenience
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":
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":
2425
1762
syslogger.setFormatter(logging.Formatter
2426
('Mandos ({}) [%(process)d]:'
2427
' %(levelname)s: %(message)s'
2428
.format(server_settings
1763
(u'Mandos (%s) [%%(process)d]:'
1764
u' %%(levelname)s: %%(message)s'
1765
% server_settings[u"servicename"]))
2431
1767
# Parse config file with clients
2432
client_config = configparser.SafeConfigParser(Client
2434
client_config.read(os.path.join(server_settings["configdir"],
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"],
2437
1779
global mandos_dbus_service
2438
1780
mandos_dbus_service = None
2441
if server_settings["socket"] != "":
2442
socketfd = server_settings["socket"]
2443
tcp_server = MandosServer((server_settings["address"],
2444
server_settings["port"]),
1782
tcp_server = MandosServer((server_settings[u"address"],
1783
server_settings[u"port"]),
2446
interface=(server_settings["interface"]
1785
interface=(server_settings[u"interface"]
2448
1787
use_ipv6=use_ipv6,
2449
1788
gnutls_priority=
2450
server_settings["priority"],
2454
pidfilename = "/run/mandos.pid"
2455
if not os.path.isdir("/run/."):
2456
pidfilename = "/var/run/mandos.pid"
1789
server_settings[u"priority"],
1792
pidfilename = u"/var/run/mandos.pid"
2459
pidfile = open(pidfilename, "w")
2460
except IOError as e:
2461
logger.error("Could not open file %r", pidfilename,
1794
pidfile = open(pidfilename, u"w")
1796
logger.error(u"Could not open file %r", pidfilename)
2464
for name in ("_mandos", "mandos", "nobody"):
1799
uid = pwd.getpwnam(u"_mandos").pw_uid
1800
gid = pwd.getpwnam(u"_mandos").pw_gid
2466
uid = pwd.getpwnam(name).pw_uid
2467
gid = pwd.getpwnam(name).pw_gid
1803
uid = pwd.getpwnam(u"mandos").pw_uid
1804
gid = pwd.getpwnam(u"mandos").pw_gid
2469
1805
except KeyError:
1807
uid = pwd.getpwnam(u"nobody").pw_uid
1808
gid = pwd.getpwnam(u"nobody").pw_gid
2477
except OSError as error:
2478
if error.errno != errno.EPERM:
1815
except OSError, error:
1816
if error[0] != 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)
2482
1828
# Enable all possible GnuTLS debugging
2488
1834
@gnutls.library.types.gnutls_log_func
2489
1835
def debug_gnutls(level, string):
2490
logger.debug("GnuTLS: %s", string[:-1])
1836
logger.debug(u"GnuTLS: %s", string[:-1])
2492
1838
(gnutls.library.functions
2493
1839
.gnutls_global_set_log_function(debug_gnutls))
2495
1841
# Redirect stdin so all checkers get /dev/null
2496
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1842
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2497
1843
os.dup2(null, sys.stdin.fileno())
2501
# Need to fork before connecting to D-Bus
2503
# Close all input and output, do double fork, etc.
2506
# multiprocessing will use threads, so before we use gobject we
2507
# need to inform gobject that threads will be used.
2508
gobject.threads_init()
1847
# No console logging
1848
logger.removeHandler(console)
2510
1851
global main_loop
2511
1852
# From the Avahi example code
2512
DBusGMainLoop(set_as_default=True)
1853
DBusGMainLoop(set_as_default=True )
2513
1854
main_loop = gobject.MainLoop()
2514
1855
bus = dbus.SystemBus()
2515
1856
# End of Avahi example code
2518
bus_name = dbus.service.BusName("se.recompile.Mandos",
1859
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2519
1860
bus, do_not_queue=True)
2520
old_bus_name = (dbus.service.BusName
2521
("se.bsnet.fukt.Mandos", bus,
2523
except dbus.exceptions.NameExistsException as e:
2524
logger.error("Disabling D-Bus:", exc_info=e)
1861
except dbus.exceptions.NameExistsException, e:
1862
logger.error(unicode(e) + u", disabling D-Bus")
2525
1863
use_dbus = False
2526
server_settings["use_dbus"] = False
1864
server_settings[u"use_dbus"] = False
2527
1865
tcp_server.use_dbus = False
2529
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2530
service = AvahiServiceToSyslog(name =
2531
server_settings["servicename"],
2532
servicetype = "_mandos._tcp",
2533
protocol = protocol, bus = bus)
2534
if server_settings["interface"]:
2535
service.interface = (if_nametoindex
2536
(str(server_settings["interface"])))
1866
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1867
service = AvahiService(name = server_settings[u"servicename"],
1868
servicetype = u"_mandos._tcp",
1869
protocol = protocol, bus = bus)
1870
if server_settings["interface"]:
1871
service.interface = (if_nametoindex
1872
(str(server_settings[u"interface"])))
1875
# Close all input and output, do double fork, etc.
2538
1878
global multiprocessing_manager
2539
1879
multiprocessing_manager = multiprocessing.Manager()
2541
1881
client_class = Client
2543
1883
client_class = functools.partial(ClientDBus, bus = bus)
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))
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):
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)
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()))
2652
1901
if not tcp_server.clients:
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",
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
2665
1916
del pidfilename
1918
signal.signal(signal.SIGINT, signal.SIG_IGN)
2667
1920
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2668
1921
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2671
@alternate_dbus_interfaces({"se.recompile.Mandos":
2672
"se.bsnet.fukt.Mandos"})
2673
class MandosDBusService(DBusObjectWithProperties):
1924
class MandosDBusService(dbus.service.Object):
2674
1925
"""A D-Bus proxy object"""
2675
1926
def __init__(self):
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")
1927
dbus.service.Object.__init__(self, bus, u"/")
1928
_interface = u"se.bsnet.fukt.Mandos"
1930
@dbus.service.signal(_interface, signature=u"o")
2686
1931
def ClientAdded(self, objpath):
2690
@dbus.service.signal(_interface, signature="ss")
1935
@dbus.service.signal(_interface, signature=u"ss")
2691
1936
def ClientNotFound(self, fingerprint, address):
2695
@dbus.service.signal(_interface, signature="os")
1940
@dbus.service.signal(_interface, signature=u"os")
2696
1941
def ClientRemoved(self, objpath, name):
2700
@dbus.service.method(_interface, out_signature="ao")
1945
@dbus.service.method(_interface, out_signature=u"ao")
2701
1946
def GetAllClients(self):
2703
1948
return dbus.Array(c.dbus_object_path
2705
tcp_server.clients.itervalues())
1949
for c in tcp_server.clients)
2707
1951
@dbus.service.method(_interface,
2708
out_signature="a{oa{sv}}")
1952
out_signature=u"a{oa{sv}}")
2709
1953
def GetAllClientsWithProperties(self):
2711
1955
return dbus.Dictionary(
2712
((c.dbus_object_path, c.GetAll(""))
2713
for c in tcp_server.clients.itervalues()),
1956
((c.dbus_object_path, c.GetAll(u""))
1957
for c in tcp_server.clients),
1958
signature=u"oa{sv}")
2716
@dbus.service.method(_interface, in_signature="o")
1960
@dbus.service.method(_interface, in_signature=u"o")
2717
1961
def RemoveClient(self, object_path):
2719
for c in tcp_server.clients.itervalues():
1963
for c in tcp_server.clients:
2720
1964
if c.dbus_object_path == object_path:
2721
del tcp_server.clients[c.name]
1965
tcp_server.clients.remove(c)
2722
1966
c.remove_from_connection()
2723
1967
# Don't signal anything except ClientRemoved
2724
1968
c.disable(quiet=True)
2735
1979
"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
2795
1982
while tcp_server.clients:
2796
name, client = tcp_server.clients.popitem()
1983
client = tcp_server.clients.pop()
2798
1985
client.remove_from_connection()
1986
client.disable_hook = None
2799
1987
# Don't signal anything except ClientRemoved
2800
1988
client.disable(quiet=True)
2802
1990
# Emit D-Bus signal
2803
mandos_dbus_service.ClientRemoved(client
1991
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2806
client_settings.clear()
2808
1994
atexit.register(cleanup)
2810
for client in tcp_server.clients.itervalues():
1996
for client in tcp_server.clients:
2812
1998
# Emit D-Bus signal
2813
1999
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2814
# Need to initiate checking of clients
2816
client.init_checker()
2818
2002
tcp_server.enable()
2819
2003
tcp_server.server_activate()
2821
2005
# Find out what port we got
2823
service.port = tcp_server.socket.getsockname()[1]
2006
service.port = tcp_server.socket.getsockname()[1]
2825
logger.info("Now listening on address %r, port %d,"
2826
" flowinfo %d, scope_id %d",
2827
*tcp_server.socket.getsockname())
2008
logger.info(u"Now listening on address %r, port %d,"
2009
" flowinfo %d, scope_id %d"
2010
% tcp_server.socket.getsockname())
2829
logger.info("Now listening on address %r, port %d",
2830
*tcp_server.socket.getsockname())
2012
logger.info(u"Now listening on address %r, port %d"
2013
% tcp_server.socket.getsockname())
2832
2015
#service.interface = tcp_server.socket.getsockname()[3]
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
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
2845
2027
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2846
2028
lambda *args, **kwargs:
2847
2029
(tcp_server.handle_request
2848
2030
(*args[2:], **kwargs) or True))
2850
logger.debug("Starting main loop")
2032
logger.debug(u"Starting main loop")
2851
2033
main_loop.run()
2852
except AvahiError as error:
2853
logger.critical("Avahi Error", exc_info=error)
2034
except AvahiError, error:
2035
logger.critical(u"AvahiError: %s", error)
2856
2038
except KeyboardInterrupt:
2858
print("", file=sys.stderr)
2859
logger.debug("Server received KeyboardInterrupt")
2860
logger.debug("Server exiting")
2041
logger.debug(u"Server received KeyboardInterrupt")
2042
logger.debug(u"Server exiting")
2861
2043
# Must run before the D-Bus bus name gets deregistered