76
88
except ImportError:
77
89
SO_BINDTODEVICE = None
82
logger = logging.Logger(u'mandos')
92
stored_state_file = "clients.pickle"
94
logger = logging.getLogger()
83
95
syslogger = (logging.handlers.SysLogHandler
84
96
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
85
address = "/dev/log"))
86
syslogger.setFormatter(logging.Formatter
87
(u'Mandos [%(process)d]: %(levelname)s:'
89
logger.addHandler(syslogger)
91
console = logging.StreamHandler()
92
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
95
logger.addHandler(console)
97
address = str("/dev/log")))
100
if_nametoindex = (ctypes.cdll.LoadLibrary
101
(ctypes.util.find_library("c"))
103
except (OSError, AttributeError):
104
def if_nametoindex(interface):
105
"Get an interface index the hard way, i.e. using fcntl()"
106
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
107
with contextlib.closing(socket.socket()) as s:
108
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
109
struct.pack(str("16s16x"),
111
interface_index = struct.unpack(str("I"),
113
return interface_index
116
def initlogger(debug, level=logging.WARNING):
117
"""init logger and add loglevel"""
119
syslogger.setFormatter(logging.Formatter
120
('Mandos [%(process)d]: %(levelname)s:'
122
logger.addHandler(syslogger)
125
console = logging.StreamHandler()
126
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
130
logger.addHandler(console)
131
logger.setLevel(level)
134
class PGPError(Exception):
135
"""Exception if encryption/decryption fails"""
139
class PGPEngine(object):
140
"""A simple class for OpenPGP symmetric encryption & decryption"""
142
self.gnupg = GnuPGInterface.GnuPG()
143
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
144
self.gnupg = GnuPGInterface.GnuPG()
145
self.gnupg.options.meta_interactive = False
146
self.gnupg.options.homedir = self.tempdir
147
self.gnupg.options.extra_args.extend(['--force-mdc',
154
def __exit__ (self, exc_type, exc_value, traceback):
162
if self.tempdir is not None:
163
# Delete contents of tempdir
164
for root, dirs, files in os.walk(self.tempdir,
166
for filename in files:
167
os.remove(os.path.join(root, filename))
169
os.rmdir(os.path.join(root, dirname))
171
os.rmdir(self.tempdir)
174
def password_encode(self, password):
175
# Passphrase can not be empty and can not contain newlines or
176
# NUL bytes. So we prefix it and hex encode it.
177
return b"mandos" + binascii.hexlify(password)
179
def encrypt(self, data, password):
180
self.gnupg.passphrase = self.password_encode(password)
181
with open(os.devnull, "w") as devnull:
183
proc = self.gnupg.run(['--symmetric'],
184
create_fhs=['stdin', 'stdout'],
185
attach_fhs={'stderr': devnull})
186
with contextlib.closing(proc.handles['stdin']) as f:
188
with contextlib.closing(proc.handles['stdout']) as f:
189
ciphertext = f.read()
193
self.gnupg.passphrase = None
196
def decrypt(self, data, password):
197
self.gnupg.passphrase = self.password_encode(password)
198
with open(os.devnull, "w") as devnull:
200
proc = self.gnupg.run(['--decrypt'],
201
create_fhs=['stdin', 'stdout'],
202
attach_fhs={'stderr': devnull})
203
with contextlib.closing(proc.handles['stdin']) as f:
205
with contextlib.closing(proc.handles['stdout']) as f:
206
decrypted_plaintext = f.read()
210
self.gnupg.passphrase = None
211
return decrypted_plaintext
97
214
class AvahiError(Exception):
98
215
def __init__(self, value, *args, **kwargs):
186
315
dbus.UInt16(self.port),
187
316
avahi.string_array_to_txt_array(self.TXT))
188
317
self.group.Commit()
189
319
def entry_group_state_changed(self, state, error):
190
320
"""Derived from the Avahi example code"""
191
logger.debug(u"Avahi state change: %i", state)
321
logger.debug("Avahi entry group state change: %i", state)
193
323
if state == avahi.ENTRY_GROUP_ESTABLISHED:
194
logger.debug(u"Zeroconf service established.")
324
logger.debug("Zeroconf service established.")
195
325
elif state == avahi.ENTRY_GROUP_COLLISION:
196
logger.warning(u"Zeroconf service name collision.")
326
logger.info("Zeroconf service name collision.")
198
328
elif state == avahi.ENTRY_GROUP_FAILURE:
199
logger.critical(u"Avahi: Error in group state changed %s",
329
logger.critical("Avahi: Error in group state changed %s",
201
raise AvahiGroupError(u"State changed: %s"
331
raise AvahiGroupError("State changed: {0!s}"
203
334
def cleanup(self):
204
335
"""Derived from the Avahi example code"""
205
336
if self.group is not None:
339
except (dbus.exceptions.UnknownMethodException,
340
dbus.exceptions.DBusException):
207
342
self.group = None
208
def server_state_changed(self, state):
345
def server_state_changed(self, state, error=None):
209
346
"""Derived from the Avahi example code"""
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
347
logger.debug("Avahi server state change: %i", state)
348
bad_states = { avahi.SERVER_INVALID:
349
"Zeroconf server invalid",
350
avahi.SERVER_REGISTERING: None,
351
avahi.SERVER_COLLISION:
352
"Zeroconf server name collision",
353
avahi.SERVER_FAILURE:
354
"Zeroconf server failure" }
355
if state in bad_states:
356
if bad_states[state] is not None:
358
logger.error(bad_states[state])
360
logger.error(bad_states[state] + ": %r", error)
213
362
elif state == avahi.SERVER_RUNNING:
366
logger.debug("Unknown state: %r", state)
368
logger.debug("Unknown state: %r: %r", state, error)
215
370
def activate(self):
216
371
"""Derived from the Avahi example code"""
217
372
if self.server is None:
218
373
self.server = dbus.Interface(
219
374
self.bus.get_object(avahi.DBUS_NAME,
220
avahi.DBUS_PATH_SERVER),
375
avahi.DBUS_PATH_SERVER,
376
follow_name_owner_changes=True),
221
377
avahi.DBUS_INTERFACE_SERVER)
222
self.server.connect_to_signal(u"StateChanged",
378
self.server.connect_to_signal("StateChanged",
223
379
self.server_state_changed)
224
380
self.server_state_changed(self.server.GetState())
382
class AvahiServiceToSyslog(AvahiService):
384
"""Add the new name to the syslog messages"""
385
ret = AvahiService.rename(self)
386
syslogger.setFormatter(logging.Formatter
387
('Mandos ({0}) [%(process)d]:'
388
' %(levelname)s: %(message)s'
392
def timedelta_to_milliseconds(td):
393
"Convert a datetime.timedelta() to milliseconds"
394
return ((td.days * 24 * 60 * 60 * 1000)
395
+ (td.seconds * 1000)
396
+ (td.microseconds // 1000))
227
398
class Client(object):
228
399
"""A representation of a client host served by this server.
231
name: string; from the config file, used in log messages and
233
fingerprint: string (40 or 32 hexadecimal digits); used to
234
uniquely identify the client
235
secret: bytestring; sent verbatim (over TLS) to client
236
host: string; available for use by the checker command
237
created: datetime.datetime(); (UTC) object creation
238
last_enabled: datetime.datetime(); (UTC)
240
last_checked_ok: datetime.datetime(); (UTC) or None
241
timeout: datetime.timedelta(); How long from last_checked_ok
242
until this client is invalid
243
interval: datetime.timedelta(); How often to start a new checker
244
disable_hook: If set, called by disable() as disable_hook(self)
402
approved: bool(); 'None' if not yet approved/disapproved
403
approval_delay: datetime.timedelta(); Time to wait for approval
404
approval_duration: datetime.timedelta(); Duration of one approval
245
405
checker: subprocess.Popen(); a running checker process used
246
406
to see if the client lives.
247
407
'None' if no process is running.
248
checker_initiator_tag: a gobject event source tag, or None
249
disable_initiator_tag: - '' -
250
checker_callback_tag: - '' -
251
checker_command: string; External command which is run to check if
252
client lives. %() expansions are done at
408
checker_callback_tag: a gobject event source tag, or None
409
checker_command: string; External command which is run to check
410
if client lives. %() expansions are done at
253
411
runtime with vars(self) as dict, so that for
254
412
instance %(name)s can be used in the command.
413
checker_initiator_tag: a gobject event source tag, or None
414
created: datetime.datetime(); (UTC) object creation
415
client_structure: Object describing what attributes a client has
416
and is used for storing the client at exit
255
417
current_checker_command: string; current running checker_command
418
disable_initiator_tag: a gobject event source tag, or None
420
fingerprint: string (40 or 32 hexadecimal digits); used to
421
uniquely identify the client
422
host: string; available for use by the checker command
423
interval: datetime.timedelta(); How often to start a new checker
424
last_approval_request: datetime.datetime(); (UTC) or None
425
last_checked_ok: datetime.datetime(); (UTC) or None
426
last_checker_status: integer between 0 and 255 reflecting exit
427
status of last checker. -1 reflects crashed
428
checker, -2 means no checker completed yet.
429
last_enabled: datetime.datetime(); (UTC) or None
430
name: string; from the config file, used in log messages and
432
secret: bytestring; sent verbatim (over TLS) to client
433
timeout: datetime.timedelta(); How long from last_checked_ok
434
until this client is disabled
435
extended_timeout: extra long timeout when secret has been sent
436
runtime_expansions: Allowed attributes for runtime expansion.
437
expires: datetime.datetime(); time (UTC) when a client will be
259
def _datetime_to_milliseconds(dt):
260
"Convert a datetime.datetime() to milliseconds"
261
return ((dt.days * 24 * 60 * 60 * 1000)
262
+ (dt.seconds * 1000)
263
+ (dt.microseconds // 1000))
441
runtime_expansions = ("approval_delay", "approval_duration",
442
"created", "enabled", "fingerprint",
443
"host", "interval", "last_checked_ok",
444
"last_enabled", "name", "timeout")
445
client_defaults = { "timeout": "5m",
446
"extended_timeout": "15m",
448
"checker": "fping -q -- %%(host)s",
450
"approval_delay": "0s",
451
"approval_duration": "1s",
452
"approved_by_default": "True",
265
456
def timeout_milliseconds(self):
266
457
"Return the 'timeout' attribute in milliseconds"
267
return self._datetime_to_milliseconds(self.timeout)
458
return timedelta_to_milliseconds(self.timeout)
460
def extended_timeout_milliseconds(self):
461
"Return the 'extended_timeout' attribute in milliseconds"
462
return timedelta_to_milliseconds(self.extended_timeout)
269
464
def interval_milliseconds(self):
270
465
"Return the 'interval' attribute in milliseconds"
271
return self._datetime_to_milliseconds(self.interval)
273
def __init__(self, name = None, disable_hook=None, config=None):
274
"""Note: the 'checker' key in 'config' sets the
275
'checker_command' attribute and *not* the 'checker'
466
return timedelta_to_milliseconds(self.interval)
468
def approval_delay_milliseconds(self):
469
return timedelta_to_milliseconds(self.approval_delay)
472
def config_parser(config):
473
"""Construct a new dict of client settings of this form:
474
{ client_name: {setting_name: value, ...}, ...}
475
with exceptions for any special settings as defined above.
476
NOTE: Must be a pure function. Must return the same result
477
value given the same arguments.
480
for client_name in config.sections():
481
section = dict(config.items(client_name))
482
client = settings[client_name] = {}
484
client["host"] = section["host"]
485
# Reformat values from string types to Python types
486
client["approved_by_default"] = config.getboolean(
487
client_name, "approved_by_default")
488
client["enabled"] = config.getboolean(client_name,
491
client["fingerprint"] = (section["fingerprint"].upper()
493
if "secret" in section:
494
client["secret"] = section["secret"].decode("base64")
495
elif "secfile" in section:
496
with open(os.path.expanduser(os.path.expandvars
497
(section["secfile"])),
499
client["secret"] = secfile.read()
501
raise TypeError("No secret or secfile for section {0}"
503
client["timeout"] = string_to_delta(section["timeout"])
504
client["extended_timeout"] = string_to_delta(
505
section["extended_timeout"])
506
client["interval"] = string_to_delta(section["interval"])
507
client["approval_delay"] = string_to_delta(
508
section["approval_delay"])
509
client["approval_duration"] = string_to_delta(
510
section["approval_duration"])
511
client["checker_command"] = section["checker"]
512
client["last_approval_request"] = None
513
client["last_checked_ok"] = None
514
client["last_checker_status"] = -2
518
def __init__(self, settings, name = None):
280
logger.debug(u"Creating client %r", self.name)
520
# adding all client settings
521
for setting, value in settings.iteritems():
522
setattr(self, setting, value)
525
if not hasattr(self, "last_enabled"):
526
self.last_enabled = datetime.datetime.utcnow()
527
if not hasattr(self, "expires"):
528
self.expires = (datetime.datetime.utcnow()
531
self.last_enabled = None
534
logger.debug("Creating client %r", self.name)
281
535
# Uppercase and remove spaces from fingerprint for later
282
536
# comparison purposes with return value from the fingerprint()
284
self.fingerprint = (config[u"fingerprint"].upper()
286
logger.debug(u" Fingerprint: %s", self.fingerprint)
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
290
with closing(open(os.path.expanduser
292
(config[u"secfile"])))) as secfile:
293
self.secret = secfile.read()
295
raise TypeError(u"No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
298
self.created = datetime.datetime.utcnow()
300
self.last_enabled = None
301
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
304
self.disable_hook = disable_hook
538
logger.debug(" Fingerprint: %s", self.fingerprint)
539
self.created = settings.get("created",
540
datetime.datetime.utcnow())
542
# attributes specific for this server instance
305
543
self.checker = None
306
544
self.checker_initiator_tag = None
307
545
self.disable_initiator_tag = None
308
546
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
310
547
self.current_checker_command = None
311
self.last_connect = None
549
self.approvals_pending = 0
550
self.changedstate = (multiprocessing_manager
551
.Condition(multiprocessing_manager
553
self.client_structure = [attr for attr in
554
self.__dict__.iterkeys()
555
if not attr.startswith("_")]
556
self.client_structure.append("client_structure")
558
for name, t in inspect.getmembers(type(self),
562
if not name.startswith("_"):
563
self.client_structure.append(name)
565
# Send notice to process children that client state has changed
566
def send_changedstate(self):
567
with self.changedstate:
568
self.changedstate.notify_all()
313
570
def enable(self):
314
571
"""Start this client's checker and timeout hooks"""
315
if getattr(self, u"enabled", False):
572
if getattr(self, "enabled", False):
316
573
# Already enabled
575
self.send_changedstate()
576
self.expires = datetime.datetime.utcnow() + self.timeout
318
578
self.last_enabled = datetime.datetime.utcnow()
581
def disable(self, quiet=True):
582
"""Disable this client."""
583
if not getattr(self, "enabled", False):
586
self.send_changedstate()
588
logger.info("Disabling client %s", self.name)
589
if getattr(self, "disable_initiator_tag", False):
590
gobject.source_remove(self.disable_initiator_tag)
591
self.disable_initiator_tag = None
593
if getattr(self, "checker_initiator_tag", False):
594
gobject.source_remove(self.checker_initiator_tag)
595
self.checker_initiator_tag = None
598
# Do not run this again if called by a gobject.timeout_add
604
def init_checker(self):
319
605
# Schedule a new checker to be started an 'interval' from now,
320
606
# and every interval from then on.
321
607
self.checker_initiator_tag = (gobject.timeout_add
322
608
(self.interval_milliseconds(),
323
609
self.start_checker))
324
# Also start a new checker *right now*.
326
610
# Schedule a disable() when 'timeout' has passed
327
611
self.disable_initiator_tag = (gobject.timeout_add
328
612
(self.timeout_milliseconds(),
333
"""Disable this client."""
334
if not getattr(self, "enabled", False):
336
logger.info(u"Disabling client %s", self.name)
337
if getattr(self, u"disable_initiator_tag", False):
338
gobject.source_remove(self.disable_initiator_tag)
339
self.disable_initiator_tag = None
340
if getattr(self, u"checker_initiator_tag", False):
341
gobject.source_remove(self.checker_initiator_tag)
342
self.checker_initiator_tag = None
344
if self.disable_hook:
345
self.disable_hook(self)
347
# Do not run this again if called by a gobject.timeout_add
351
self.disable_hook = None
614
# Also start a new checker *right now*.
354
617
def checker_callback(self, pid, condition, command):
355
618
"""The checker has completed, so take appropriate actions."""
356
619
self.checker_callback_tag = None
357
620
self.checker = None
358
621
if os.WIFEXITED(condition):
359
exitstatus = os.WEXITSTATUS(condition)
361
logger.info(u"Checker for %(name)s succeeded",
622
self.last_checker_status = os.WEXITSTATUS(condition)
623
if self.last_checker_status == 0:
624
logger.info("Checker for %(name)s succeeded",
363
626
self.checked_ok()
365
logger.info(u"Checker for %(name)s failed",
628
logger.info("Checker for %(name)s failed",
368
logger.warning(u"Checker for %(name)s crashed?",
631
self.last_checker_status = -1
632
logger.warning("Checker for %(name)s crashed?",
371
635
def checked_ok(self):
372
"""Bump up the timeout for this client.
374
This should only be called when the client has been seen,
636
"""Assert that the client has been seen, alive and well."""
377
637
self.last_checked_ok = datetime.datetime.utcnow()
378
gobject.source_remove(self.disable_initiator_tag)
379
self.disable_initiator_tag = (gobject.timeout_add
380
(self.timeout_milliseconds(),
638
self.last_checker_status = 0
641
def bump_timeout(self, timeout=None):
642
"""Bump up the timeout for this client."""
644
timeout = self.timeout
645
if self.disable_initiator_tag is not None:
646
gobject.source_remove(self.disable_initiator_tag)
647
if getattr(self, "enabled", False):
648
self.disable_initiator_tag = (gobject.timeout_add
649
(timedelta_to_milliseconds
650
(timeout), self.disable))
651
self.expires = datetime.datetime.utcnow() + timeout
653
def need_approval(self):
654
self.last_approval_request = datetime.datetime.utcnow()
383
656
def start_checker(self):
384
657
"""Start a new checker subprocess if one is not running.
453
733
if self.checker_callback_tag:
454
734
gobject.source_remove(self.checker_callback_tag)
455
735
self.checker_callback_tag = None
456
if getattr(self, u"checker", None) is None:
736
if getattr(self, "checker", None) is None:
458
logger.debug(u"Stopping checker for %(name)s", vars(self))
738
logger.debug("Stopping checker for %(name)s", vars(self))
460
os.kill(self.checker.pid, signal.SIGTERM)
740
self.checker.terminate()
462
742
#if self.checker.poll() is None:
463
# os.kill(self.checker.pid, signal.SIGKILL)
464
except OSError, error:
743
# self.checker.kill()
744
except OSError as error:
465
745
if error.errno != errno.ESRCH: # No such process
467
747
self.checker = None
469
def still_valid(self):
470
"""Has the timeout not yet passed for this client?"""
471
if not getattr(self, u"enabled", False):
473
now = datetime.datetime.utcnow()
474
if self.last_checked_ok is None:
475
return now < (self.created + self.timeout)
477
return now < (self.last_checked_ok + self.timeout)
480
class ClientDBus(Client, dbus.service.Object):
750
def dbus_service_property(dbus_interface, signature="v",
751
access="readwrite", byte_arrays=False):
752
"""Decorators for marking methods of a DBusObjectWithProperties to
753
become properties on the D-Bus.
755
The decorated method will be called with no arguments by "Get"
756
and with one argument by "Set".
758
The parameters, where they are supported, are the same as
759
dbus.service.method, except there is only "signature", since the
760
type from Get() and the type sent to Set() is the same.
762
# Encoding deeply encoded byte arrays is not supported yet by the
763
# "Set" method, so we fail early here:
764
if byte_arrays and signature != "ay":
765
raise ValueError("Byte arrays not supported for non-'ay'"
766
" signature {0!r}".format(signature))
768
func._dbus_is_property = True
769
func._dbus_interface = dbus_interface
770
func._dbus_signature = signature
771
func._dbus_access = access
772
func._dbus_name = func.__name__
773
if func._dbus_name.endswith("_dbus_property"):
774
func._dbus_name = func._dbus_name[:-14]
775
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
780
def dbus_interface_annotations(dbus_interface):
781
"""Decorator for marking functions returning interface annotations
785
@dbus_interface_annotations("org.example.Interface")
786
def _foo(self): # Function name does not matter
787
return {"org.freedesktop.DBus.Deprecated": "true",
788
"org.freedesktop.DBus.Property.EmitsChangedSignal":
792
func._dbus_is_interface = True
793
func._dbus_interface = dbus_interface
794
func._dbus_name = dbus_interface
799
def dbus_annotations(annotations):
800
"""Decorator to annotate D-Bus methods, signals or properties
803
@dbus_service_property("org.example.Interface", signature="b",
805
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
806
"org.freedesktop.DBus.Property."
807
"EmitsChangedSignal": "false"})
808
def Property_dbus_property(self):
809
return dbus.Boolean(False)
812
func._dbus_annotations = annotations
817
class DBusPropertyException(dbus.exceptions.DBusException):
818
"""A base class for D-Bus property-related exceptions
820
def __unicode__(self):
821
return unicode(str(self))
824
class DBusPropertyAccessException(DBusPropertyException):
825
"""A property's access permissions disallows an operation.
830
class DBusPropertyNotFound(DBusPropertyException):
831
"""An attempt was made to access a non-existing property.
836
class DBusObjectWithProperties(dbus.service.Object):
837
"""A D-Bus object with properties.
839
Classes inheriting from this can use the dbus_service_property
840
decorator to expose methods as D-Bus properties. It exposes the
841
standard Get(), Set(), and GetAll() methods on the D-Bus.
845
def _is_dbus_thing(thing):
846
"""Returns a function testing if an attribute is a D-Bus thing
848
If called like _is_dbus_thing("method") it returns a function
849
suitable for use as predicate to inspect.getmembers().
851
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
854
def _get_all_dbus_things(self, thing):
855
"""Returns a generator of (name, attribute) pairs
857
return ((getattr(athing.__get__(self), "_dbus_name",
859
athing.__get__(self))
860
for cls in self.__class__.__mro__
862
inspect.getmembers(cls,
863
self._is_dbus_thing(thing)))
865
def _get_dbus_property(self, interface_name, property_name):
866
"""Returns a bound method if one exists which is a D-Bus
867
property with the specified name and interface.
869
for cls in self.__class__.__mro__:
870
for name, value in (inspect.getmembers
872
self._is_dbus_thing("property"))):
873
if (value._dbus_name == property_name
874
and value._dbus_interface == interface_name):
875
return value.__get__(self)
878
raise DBusPropertyNotFound(self.dbus_object_path + ":"
879
+ interface_name + "."
882
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
884
def Get(self, interface_name, property_name):
885
"""Standard D-Bus property Get() method, see D-Bus standard.
887
prop = self._get_dbus_property(interface_name, property_name)
888
if prop._dbus_access == "write":
889
raise DBusPropertyAccessException(property_name)
891
if not hasattr(value, "variant_level"):
893
return type(value)(value, variant_level=value.variant_level+1)
895
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
896
def Set(self, interface_name, property_name, value):
897
"""Standard D-Bus property Set() method, see D-Bus standard.
899
prop = self._get_dbus_property(interface_name, property_name)
900
if prop._dbus_access == "read":
901
raise DBusPropertyAccessException(property_name)
902
if prop._dbus_get_args_options["byte_arrays"]:
903
# The byte_arrays option is not supported yet on
904
# signatures other than "ay".
905
if prop._dbus_signature != "ay":
907
value = dbus.ByteArray(b''.join(chr(byte)
911
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
912
out_signature="a{sv}")
913
def GetAll(self, interface_name):
914
"""Standard D-Bus property GetAll() method, see D-Bus
917
Note: Will not include properties with access="write".
920
for name, prop in self._get_all_dbus_things("property"):
922
and interface_name != prop._dbus_interface):
923
# Interface non-empty but did not match
925
# Ignore write-only properties
926
if prop._dbus_access == "write":
929
if not hasattr(value, "variant_level"):
930
properties[name] = value
932
properties[name] = type(value)(value, variant_level=
933
value.variant_level+1)
934
return dbus.Dictionary(properties, signature="sv")
936
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
938
path_keyword='object_path',
939
connection_keyword='connection')
940
def Introspect(self, object_path, connection):
941
"""Overloading of standard D-Bus method.
943
Inserts property tags and interface annotation tags.
945
xmlstring = dbus.service.Object.Introspect(self, object_path,
948
document = xml.dom.minidom.parseString(xmlstring)
949
def make_tag(document, name, prop):
950
e = document.createElement("property")
951
e.setAttribute("name", name)
952
e.setAttribute("type", prop._dbus_signature)
953
e.setAttribute("access", prop._dbus_access)
955
for if_tag in document.getElementsByTagName("interface"):
957
for tag in (make_tag(document, name, prop)
959
in self._get_all_dbus_things("property")
960
if prop._dbus_interface
961
== if_tag.getAttribute("name")):
962
if_tag.appendChild(tag)
963
# Add annotation tags
964
for typ in ("method", "signal", "property"):
965
for tag in if_tag.getElementsByTagName(typ):
967
for name, prop in (self.
968
_get_all_dbus_things(typ)):
969
if (name == tag.getAttribute("name")
970
and prop._dbus_interface
971
== if_tag.getAttribute("name")):
972
annots.update(getattr
976
for name, value in annots.iteritems():
977
ann_tag = document.createElement(
979
ann_tag.setAttribute("name", name)
980
ann_tag.setAttribute("value", value)
981
tag.appendChild(ann_tag)
982
# Add interface annotation tags
983
for annotation, value in dict(
984
itertools.chain.from_iterable(
985
annotations().iteritems()
986
for name, annotations in
987
self._get_all_dbus_things("interface")
988
if name == if_tag.getAttribute("name")
990
ann_tag = document.createElement("annotation")
991
ann_tag.setAttribute("name", annotation)
992
ann_tag.setAttribute("value", value)
993
if_tag.appendChild(ann_tag)
994
# Add the names to the return values for the
995
# "org.freedesktop.DBus.Properties" methods
996
if (if_tag.getAttribute("name")
997
== "org.freedesktop.DBus.Properties"):
998
for cn in if_tag.getElementsByTagName("method"):
999
if cn.getAttribute("name") == "Get":
1000
for arg in cn.getElementsByTagName("arg"):
1001
if (arg.getAttribute("direction")
1003
arg.setAttribute("name", "value")
1004
elif cn.getAttribute("name") == "GetAll":
1005
for arg in cn.getElementsByTagName("arg"):
1006
if (arg.getAttribute("direction")
1008
arg.setAttribute("name", "props")
1009
xmlstring = document.toxml("utf-8")
1011
except (AttributeError, xml.dom.DOMException,
1012
xml.parsers.expat.ExpatError) as error:
1013
logger.error("Failed to override Introspection method",
1018
def datetime_to_dbus (dt, variant_level=0):
1019
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1021
return dbus.String("", variant_level = variant_level)
1022
return dbus.String(dt.isoformat(),
1023
variant_level=variant_level)
1026
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1027
"""A class decorator; applied to a subclass of
1028
dbus.service.Object, it will add alternate D-Bus attributes with
1029
interface names according to the "alt_interface_names" mapping.
1032
@alternate_dbus_names({"org.example.Interface":
1033
"net.example.AlternateInterface"})
1034
class SampleDBusObject(dbus.service.Object):
1035
@dbus.service.method("org.example.Interface")
1036
def SampleDBusMethod():
1039
The above "SampleDBusMethod" on "SampleDBusObject" will be
1040
reachable via two interfaces: "org.example.Interface" and
1041
"net.example.AlternateInterface", the latter of which will have
1042
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1043
"true", unless "deprecate" is passed with a False value.
1045
This works for methods and signals, and also for D-Bus properties
1046
(from DBusObjectWithProperties) and interfaces (from the
1047
dbus_interface_annotations decorator).
1050
for orig_interface_name, alt_interface_name in (
1051
alt_interface_names.iteritems()):
1053
interface_names = set()
1054
# Go though all attributes of the class
1055
for attrname, attribute in inspect.getmembers(cls):
1056
# Ignore non-D-Bus attributes, and D-Bus attributes
1057
# with the wrong interface name
1058
if (not hasattr(attribute, "_dbus_interface")
1059
or not attribute._dbus_interface
1060
.startswith(orig_interface_name)):
1062
# Create an alternate D-Bus interface name based on
1064
alt_interface = (attribute._dbus_interface
1065
.replace(orig_interface_name,
1066
alt_interface_name))
1067
interface_names.add(alt_interface)
1068
# Is this a D-Bus signal?
1069
if getattr(attribute, "_dbus_is_signal", False):
1070
# Extract the original non-method function by
1072
nonmethod_func = (dict(
1073
zip(attribute.func_code.co_freevars,
1074
attribute.__closure__))["func"]
1076
# Create a new, but exactly alike, function
1077
# object, and decorate it to be a new D-Bus signal
1078
# with the alternate D-Bus interface name
1079
new_function = (dbus.service.signal
1081
attribute._dbus_signature)
1082
(types.FunctionType(
1083
nonmethod_func.func_code,
1084
nonmethod_func.func_globals,
1085
nonmethod_func.func_name,
1086
nonmethod_func.func_defaults,
1087
nonmethod_func.func_closure)))
1088
# Copy annotations, if any
1090
new_function._dbus_annotations = (
1091
dict(attribute._dbus_annotations))
1092
except AttributeError:
1094
# Define a creator of a function to call both the
1095
# original and alternate functions, so both the
1096
# original and alternate signals gets sent when
1097
# the function is called
1098
def fixscope(func1, func2):
1099
"""This function is a scope container to pass
1100
func1 and func2 to the "call_both" function
1101
outside of its arguments"""
1102
def call_both(*args, **kwargs):
1103
"""This function will emit two D-Bus
1104
signals by calling func1 and func2"""
1105
func1(*args, **kwargs)
1106
func2(*args, **kwargs)
1108
# Create the "call_both" function and add it to
1110
attr[attrname] = fixscope(attribute, new_function)
1111
# Is this a D-Bus method?
1112
elif getattr(attribute, "_dbus_is_method", False):
1113
# Create a new, but exactly alike, function
1114
# object. Decorate it to be a new D-Bus method
1115
# with the alternate D-Bus interface name. Add it
1117
attr[attrname] = (dbus.service.method
1119
attribute._dbus_in_signature,
1120
attribute._dbus_out_signature)
1122
(attribute.func_code,
1123
attribute.func_globals,
1124
attribute.func_name,
1125
attribute.func_defaults,
1126
attribute.func_closure)))
1127
# Copy annotations, if any
1129
attr[attrname]._dbus_annotations = (
1130
dict(attribute._dbus_annotations))
1131
except AttributeError:
1133
# Is this a D-Bus property?
1134
elif getattr(attribute, "_dbus_is_property", False):
1135
# Create a new, but exactly alike, function
1136
# object, and decorate it to be a new D-Bus
1137
# property with the alternate D-Bus interface
1138
# name. Add it to the class.
1139
attr[attrname] = (dbus_service_property
1141
attribute._dbus_signature,
1142
attribute._dbus_access,
1144
._dbus_get_args_options
1147
(attribute.func_code,
1148
attribute.func_globals,
1149
attribute.func_name,
1150
attribute.func_defaults,
1151
attribute.func_closure)))
1152
# Copy annotations, if any
1154
attr[attrname]._dbus_annotations = (
1155
dict(attribute._dbus_annotations))
1156
except AttributeError:
1158
# Is this a D-Bus interface?
1159
elif getattr(attribute, "_dbus_is_interface", False):
1160
# Create a new, but exactly alike, function
1161
# object. Decorate it to be a new D-Bus interface
1162
# with the alternate D-Bus interface name. Add it
1164
attr[attrname] = (dbus_interface_annotations
1167
(attribute.func_code,
1168
attribute.func_globals,
1169
attribute.func_name,
1170
attribute.func_defaults,
1171
attribute.func_closure)))
1173
# Deprecate all alternate interfaces
1174
iname="_AlternateDBusNames_interface_annotation{0}"
1175
for interface_name in interface_names:
1176
@dbus_interface_annotations(interface_name)
1178
return { "org.freedesktop.DBus.Deprecated":
1180
# Find an unused name
1181
for aname in (iname.format(i)
1182
for i in itertools.count()):
1183
if aname not in attr:
1187
# Replace the class with a new subclass of it with
1188
# methods, signals, etc. as created above.
1189
cls = type(b"{0}Alternate".format(cls.__name__),
1195
@alternate_dbus_interfaces({"se.recompile.Mandos":
1196
"se.bsnet.fukt.Mandos"})
1197
class ClientDBus(Client, DBusObjectWithProperties):
481
1198
"""A Client class using D-Bus
484
1201
dbus_object_path: dbus.ObjectPath
485
1202
bus: dbus.SystemBus()
1205
runtime_expansions = (Client.runtime_expansions
1206
+ ("dbus_object_path",))
487
1208
# dbus.service.Object doesn't use super(), so we can't either.
489
1210
def __init__(self, bus = None, *args, **kwargs):
491
1212
Client.__init__(self, *args, **kwargs)
492
1213
# Only now, when this client is initialized, can it show up on
1215
client_object_name = unicode(self.name).translate(
1216
{ord("."): ord("_"),
1217
ord("-"): ord("_")})
494
1218
self.dbus_object_path = (dbus.ObjectPath
496
+ self.name.replace(u".", u"_")))
497
dbus.service.Object.__init__(self, self.bus,
498
self.dbus_object_path)
501
def _datetime_to_dbus(dt, variant_level=0):
502
"""Convert a UTC datetime.datetime() to a D-Bus type."""
503
return dbus.String(dt.isoformat(),
504
variant_level=variant_level)
507
oldstate = getattr(self, u"enabled", False)
508
r = Client.enable(self)
509
if oldstate != self.enabled:
511
self.PropertyChanged(dbus.String(u"enabled"),
512
dbus.Boolean(True, variant_level=1))
513
self.PropertyChanged(
514
dbus.String(u"last_enabled"),
515
self._datetime_to_dbus(self.last_enabled,
519
def disable(self, signal = True):
520
oldstate = getattr(self, u"enabled", False)
521
r = Client.disable(self)
522
if signal and oldstate != self.enabled:
524
self.PropertyChanged(dbus.String(u"enabled"),
525
dbus.Boolean(False, variant_level=1))
1219
("/clients/" + client_object_name))
1220
DBusObjectWithProperties.__init__(self, self.bus,
1221
self.dbus_object_path)
1223
def notifychangeproperty(transform_func,
1224
dbus_name, type_func=lambda x: x,
1226
""" Modify a variable so that it's a property which announces
1227
its changes to DBus.
1229
transform_fun: Function that takes a value and a variant_level
1230
and transforms it to a D-Bus type.
1231
dbus_name: D-Bus name of the variable
1232
type_func: Function that transform the value before sending it
1233
to the D-Bus. Default: no transform
1234
variant_level: D-Bus variant level. Default: 1
1236
attrname = "_{0}".format(dbus_name)
1237
def setter(self, value):
1238
if hasattr(self, "dbus_object_path"):
1239
if (not hasattr(self, attrname) or
1240
type_func(getattr(self, attrname, None))
1241
!= type_func(value)):
1242
dbus_value = transform_func(type_func(value),
1245
self.PropertyChanged(dbus.String(dbus_name),
1247
setattr(self, attrname, value)
1249
return property(lambda self: getattr(self, attrname), setter)
1251
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1252
approvals_pending = notifychangeproperty(dbus.Boolean,
1255
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1256
last_enabled = notifychangeproperty(datetime_to_dbus,
1258
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1259
type_func = lambda checker:
1260
checker is not None)
1261
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1263
last_checker_status = notifychangeproperty(dbus.Int16,
1264
"LastCheckerStatus")
1265
last_approval_request = notifychangeproperty(
1266
datetime_to_dbus, "LastApprovalRequest")
1267
approved_by_default = notifychangeproperty(dbus.Boolean,
1268
"ApprovedByDefault")
1269
approval_delay = notifychangeproperty(dbus.UInt64,
1272
timedelta_to_milliseconds)
1273
approval_duration = notifychangeproperty(
1274
dbus.UInt64, "ApprovalDuration",
1275
type_func = timedelta_to_milliseconds)
1276
host = notifychangeproperty(dbus.String, "Host")
1277
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1279
timedelta_to_milliseconds)
1280
extended_timeout = notifychangeproperty(
1281
dbus.UInt64, "ExtendedTimeout",
1282
type_func = timedelta_to_milliseconds)
1283
interval = notifychangeproperty(dbus.UInt64,
1286
timedelta_to_milliseconds)
1287
checker_command = notifychangeproperty(dbus.String, "Checker")
1289
del notifychangeproperty
528
1291
def __del__(self, *args, **kwargs):
530
1293
self.remove_from_connection()
531
1294
except LookupError:
533
if hasattr(dbus.service.Object, u"__del__"):
534
dbus.service.Object.__del__(self, *args, **kwargs)
1296
if hasattr(DBusObjectWithProperties, "__del__"):
1297
DBusObjectWithProperties.__del__(self, *args, **kwargs)
535
1298
Client.__del__(self, *args, **kwargs)
537
1300
def checker_callback(self, pid, condition, command,
538
1301
*args, **kwargs):
539
1302
self.checker_callback_tag = None
540
1303
self.checker = None
542
self.PropertyChanged(dbus.String(u"checker_running"),
543
dbus.Boolean(False, variant_level=1))
544
1304
if os.WIFEXITED(condition):
545
1305
exitstatus = os.WEXITSTATUS(condition)
546
1306
# Emit D-Bus signal
577
1328
and old_checker_pid != self.checker.pid):
578
1329
# Emit D-Bus signal
579
1330
self.CheckerStarted(self.current_checker_command)
580
self.PropertyChanged(
581
dbus.String(u"checker_running"),
582
dbus.Boolean(True, variant_level=1))
585
def stop_checker(self, *args, **kwargs):
586
old_checker = getattr(self, u"checker", None)
587
r = Client.stop_checker(self, *args, **kwargs)
588
if (old_checker is not None
589
and getattr(self, u"checker", None) is None):
590
self.PropertyChanged(dbus.String(u"checker_running"),
591
dbus.Boolean(False, variant_level=1))
594
## D-Bus methods & signals
595
_interface = u"se.bsnet.fukt.Mandos.Client"
598
@dbus.service.method(_interface)
600
return self.checked_ok()
1333
def _reset_approved(self):
1334
self.approved = None
1337
def approve(self, value=True):
1338
self.send_changedstate()
1339
self.approved = value
1340
gobject.timeout_add(timedelta_to_milliseconds
1341
(self.approval_duration),
1342
self._reset_approved)
1344
## D-Bus methods, signals & properties
1345
_interface = "se.recompile.Mandos.Client"
1349
@dbus_interface_annotations(_interface)
1351
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
602
1356
# CheckerCompleted - signal
603
@dbus.service.signal(_interface, signature=u"nxs")
1357
@dbus.service.signal(_interface, signature="nxs")
604
1358
def CheckerCompleted(self, exitcode, waitstatus, command):
608
1362
# CheckerStarted - signal
609
@dbus.service.signal(_interface, signature=u"s")
1363
@dbus.service.signal(_interface, signature="s")
610
1364
def CheckerStarted(self, command):
614
# GetAllProperties - method
615
@dbus.service.method(_interface, out_signature=u"a{sv}")
616
def GetAllProperties(self):
618
return dbus.Dictionary({
619
dbus.String(u"name"):
620
dbus.String(self.name, variant_level=1),
621
dbus.String(u"fingerprint"):
622
dbus.String(self.fingerprint, variant_level=1),
623
dbus.String(u"host"):
624
dbus.String(self.host, variant_level=1),
625
dbus.String(u"created"):
626
self._datetime_to_dbus(self.created,
628
dbus.String(u"last_enabled"):
629
(self._datetime_to_dbus(self.last_enabled,
631
if self.last_enabled is not None
632
else dbus.Boolean(False, variant_level=1)),
633
dbus.String(u"enabled"):
634
dbus.Boolean(self.enabled, variant_level=1),
635
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
638
if self.last_checked_ok is not None
639
else dbus.Boolean (False, variant_level=1)),
640
dbus.String(u"timeout"):
641
dbus.UInt64(self.timeout_milliseconds(),
643
dbus.String(u"interval"):
644
dbus.UInt64(self.interval_milliseconds(),
646
dbus.String(u"checker"):
647
dbus.String(self.checker_command,
649
dbus.String(u"checker_running"):
650
dbus.Boolean(self.checker is not None,
652
dbus.String(u"object_path"):
653
dbus.ObjectPath(self.dbus_object_path,
657
# IsStillValid - method
658
@dbus.service.method(_interface, out_signature=u"b")
659
def IsStillValid(self):
660
return self.still_valid()
662
1368
# PropertyChanged - signal
663
@dbus.service.signal(_interface, signature=u"sv")
1369
@dbus.service.signal(_interface, signature="sv")
664
1370
def PropertyChanged(self, property, value):
668
# ReceivedSecret - signal
1374
# GotSecret - signal
669
1375
@dbus.service.signal(_interface)
670
def ReceivedSecret(self):
1376
def GotSecret(self):
1378
Is sent after a successful transfer of secret from the Mandos
1379
server to mandos-client
674
1383
# Rejected - signal
675
@dbus.service.signal(_interface)
1384
@dbus.service.signal(_interface, signature="s")
1385
def Rejected(self, reason):
680
# SetChecker - method
681
@dbus.service.method(_interface, in_signature=u"s")
682
def SetChecker(self, checker):
683
"D-Bus setter method"
684
self.checker_command = checker
686
self.PropertyChanged(dbus.String(u"checker"),
687
dbus.String(self.checker_command,
691
@dbus.service.method(_interface, in_signature=u"s")
692
def SetHost(self, host):
693
"D-Bus setter method"
696
self.PropertyChanged(dbus.String(u"host"),
697
dbus.String(self.host, variant_level=1))
699
# SetInterval - method
700
@dbus.service.method(_interface, in_signature=u"t")
701
def SetInterval(self, milliseconds):
702
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
704
self.PropertyChanged(dbus.String(u"interval"),
705
(dbus.UInt64(self.interval_milliseconds(),
709
@dbus.service.method(_interface, in_signature=u"ay",
711
def SetSecret(self, secret):
712
"D-Bus setter method"
713
self.secret = str(secret)
715
# SetTimeout - method
716
@dbus.service.method(_interface, in_signature=u"t")
717
def SetTimeout(self, milliseconds):
718
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
720
self.PropertyChanged(dbus.String(u"timeout"),
721
(dbus.UInt64(self.timeout_milliseconds(),
1389
# NeedApproval - signal
1390
@dbus.service.signal(_interface, signature="tb")
1391
def NeedApproval(self, timeout, default):
1393
return self.need_approval()
1398
@dbus.service.method(_interface, in_signature="b")
1399
def Approve(self, value):
1402
# CheckedOK - method
1403
@dbus.service.method(_interface)
1404
def CheckedOK(self):
724
1407
# Enable - method
725
1408
@dbus.service.method(_interface)
744
1427
def StopChecker(self):
745
1428
self.stop_checker()
1432
# ApprovalPending - property
1433
@dbus_service_property(_interface, signature="b", access="read")
1434
def ApprovalPending_dbus_property(self):
1435
return dbus.Boolean(bool(self.approvals_pending))
1437
# ApprovedByDefault - property
1438
@dbus_service_property(_interface, signature="b",
1440
def ApprovedByDefault_dbus_property(self, value=None):
1441
if value is None: # get
1442
return dbus.Boolean(self.approved_by_default)
1443
self.approved_by_default = bool(value)
1445
# ApprovalDelay - property
1446
@dbus_service_property(_interface, signature="t",
1448
def ApprovalDelay_dbus_property(self, value=None):
1449
if value is None: # get
1450
return dbus.UInt64(self.approval_delay_milliseconds())
1451
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1453
# ApprovalDuration - property
1454
@dbus_service_property(_interface, signature="t",
1456
def ApprovalDuration_dbus_property(self, value=None):
1457
if value is None: # get
1458
return dbus.UInt64(timedelta_to_milliseconds(
1459
self.approval_duration))
1460
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1463
@dbus_service_property(_interface, signature="s", access="read")
1464
def Name_dbus_property(self):
1465
return dbus.String(self.name)
1467
# Fingerprint - property
1468
@dbus_service_property(_interface, signature="s", access="read")
1469
def Fingerprint_dbus_property(self):
1470
return dbus.String(self.fingerprint)
1473
@dbus_service_property(_interface, signature="s",
1475
def Host_dbus_property(self, value=None):
1476
if value is None: # get
1477
return dbus.String(self.host)
1478
self.host = unicode(value)
1480
# Created - property
1481
@dbus_service_property(_interface, signature="s", access="read")
1482
def Created_dbus_property(self):
1483
return datetime_to_dbus(self.created)
1485
# LastEnabled - property
1486
@dbus_service_property(_interface, signature="s", access="read")
1487
def LastEnabled_dbus_property(self):
1488
return datetime_to_dbus(self.last_enabled)
1490
# Enabled - property
1491
@dbus_service_property(_interface, signature="b",
1493
def Enabled_dbus_property(self, value=None):
1494
if value is None: # get
1495
return dbus.Boolean(self.enabled)
1501
# LastCheckedOK - property
1502
@dbus_service_property(_interface, signature="s",
1504
def LastCheckedOK_dbus_property(self, value=None):
1505
if value is not None:
1508
return datetime_to_dbus(self.last_checked_ok)
1510
# LastCheckerStatus - property
1511
@dbus_service_property(_interface, signature="n",
1513
def LastCheckerStatus_dbus_property(self):
1514
return dbus.Int16(self.last_checker_status)
1516
# Expires - property
1517
@dbus_service_property(_interface, signature="s", access="read")
1518
def Expires_dbus_property(self):
1519
return datetime_to_dbus(self.expires)
1521
# LastApprovalRequest - property
1522
@dbus_service_property(_interface, signature="s", access="read")
1523
def LastApprovalRequest_dbus_property(self):
1524
return datetime_to_dbus(self.last_approval_request)
1526
# Timeout - property
1527
@dbus_service_property(_interface, signature="t",
1529
def Timeout_dbus_property(self, value=None):
1530
if value is None: # get
1531
return dbus.UInt64(self.timeout_milliseconds())
1532
self.timeout = datetime.timedelta(0, 0, 0, value)
1533
# Reschedule timeout
1535
now = datetime.datetime.utcnow()
1536
time_to_die = timedelta_to_milliseconds(
1537
(self.last_checked_ok + self.timeout) - now)
1538
if time_to_die <= 0:
1539
# The timeout has passed
1542
self.expires = (now +
1543
datetime.timedelta(milliseconds =
1545
if (getattr(self, "disable_initiator_tag", None)
1548
gobject.source_remove(self.disable_initiator_tag)
1549
self.disable_initiator_tag = (gobject.timeout_add
1553
# ExtendedTimeout - property
1554
@dbus_service_property(_interface, signature="t",
1556
def ExtendedTimeout_dbus_property(self, value=None):
1557
if value is None: # get
1558
return dbus.UInt64(self.extended_timeout_milliseconds())
1559
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1561
# Interval - property
1562
@dbus_service_property(_interface, signature="t",
1564
def Interval_dbus_property(self, value=None):
1565
if value is None: # get
1566
return dbus.UInt64(self.interval_milliseconds())
1567
self.interval = datetime.timedelta(0, 0, 0, value)
1568
if getattr(self, "checker_initiator_tag", None) is None:
1571
# Reschedule checker run
1572
gobject.source_remove(self.checker_initiator_tag)
1573
self.checker_initiator_tag = (gobject.timeout_add
1574
(value, self.start_checker))
1575
self.start_checker() # Start one now, too
1577
# Checker - property
1578
@dbus_service_property(_interface, signature="s",
1580
def Checker_dbus_property(self, value=None):
1581
if value is None: # get
1582
return dbus.String(self.checker_command)
1583
self.checker_command = unicode(value)
1585
# CheckerRunning - property
1586
@dbus_service_property(_interface, signature="b",
1588
def CheckerRunning_dbus_property(self, value=None):
1589
if value is None: # get
1590
return dbus.Boolean(self.checker is not None)
1592
self.start_checker()
1596
# ObjectPath - property
1597
@dbus_service_property(_interface, signature="o", access="read")
1598
def ObjectPath_dbus_property(self):
1599
return self.dbus_object_path # is already a dbus.ObjectPath
1602
@dbus_service_property(_interface, signature="ay",
1603
access="write", byte_arrays=True)
1604
def Secret_dbus_property(self, value):
1605
self.secret = str(value)
1610
class ProxyClient(object):
1611
def __init__(self, child_pipe, fpr, address):
1612
self._pipe = child_pipe
1613
self._pipe.send(('init', fpr, address))
1614
if not self._pipe.recv():
1617
def __getattribute__(self, name):
1619
return super(ProxyClient, self).__getattribute__(name)
1620
self._pipe.send(('getattr', name))
1621
data = self._pipe.recv()
1622
if data[0] == 'data':
1624
if data[0] == 'function':
1625
def func(*args, **kwargs):
1626
self._pipe.send(('funcall', name, args, kwargs))
1627
return self._pipe.recv()[1]
1630
def __setattr__(self, name, value):
1632
return super(ProxyClient, self).__setattr__(name, value)
1633
self._pipe.send(('setattr', name, value))
750
1636
class ClientHandler(socketserver.BaseRequestHandler, object):
751
1637
"""A class to handle client connections.
754
1640
Note: This will run in its own forked process."""
756
1642
def handle(self):
757
logger.info(u"TCP connection from: %s",
758
unicode(self.client_address))
759
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
760
# Open IPC pipe to parent process
761
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1643
with contextlib.closing(self.server.child_pipe) as child_pipe:
1644
logger.info("TCP connection from: %s",
1645
unicode(self.client_address))
1646
logger.debug("Pipe FD: %d",
1647
self.server.child_pipe.fileno())
762
1649
session = (gnutls.connection
763
1650
.ClientSession(self.request,
764
1651
gnutls.connection
765
1652
.X509Credentials()))
767
line = self.request.makefile().readline()
768
logger.debug(u"Protocol version: %r", line)
770
if int(line.strip().split()[0]) > 1:
772
except (ValueError, IndexError, RuntimeError), error:
773
logger.error(u"Unknown protocol version: %s", error)
776
1654
# Note: gnutls.connection.X509Credentials is really a
777
1655
# generic GnuTLS certificate credentials object so long as
778
1656
# no X.509 keys are added to it. Therefore, we can use it
779
1657
# here despite using OpenPGP certificates.
781
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
782
# u"+AES-256-CBC", u"+SHA1",
783
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1659
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1660
# "+AES-256-CBC", "+SHA1",
1661
# "+COMP-NULL", "+CTYPE-OPENPGP",
785
1663
# Use a fallback default, since this MUST be set.
786
1664
priority = self.server.gnutls_priority
787
1665
if priority is None:
789
1667
(gnutls.library.functions
790
1668
.gnutls_priority_set_direct(session._c_object,
791
1669
priority, None))
1671
# Start communication using the Mandos protocol
1672
# Get protocol number
1673
line = self.request.makefile().readline()
1674
logger.debug("Protocol version: %r", line)
1676
if int(line.strip().split()[0]) > 1:
1678
except (ValueError, IndexError, RuntimeError) as error:
1679
logger.error("Unknown protocol version: %s", error)
1682
# Start GnuTLS connection
794
1684
session.handshake()
795
except gnutls.errors.GNUTLSError, error:
796
logger.warning(u"Handshake failed: %s", error)
1685
except gnutls.errors.GNUTLSError as error:
1686
logger.warning("Handshake failed: %s", error)
797
1687
# Do not run session.bye() here: the session is not
798
1688
# established. Just abandon the request.
800
logger.debug(u"Handshake succeeded")
1690
logger.debug("Handshake succeeded")
1692
approval_required = False
802
fpr = self.fingerprint(self.peer_certificate(session))
803
except (TypeError, gnutls.errors.GNUTLSError), error:
804
logger.warning(u"Bad certificate: %s", error)
807
logger.debug(u"Fingerprint: %s", fpr)
1695
fpr = self.fingerprint(self.peer_certificate
1698
gnutls.errors.GNUTLSError) as error:
1699
logger.warning("Bad certificate: %s", error)
1701
logger.debug("Fingerprint: %s", fpr)
1704
client = ProxyClient(child_pipe, fpr,
1705
self.client_address)
1709
if client.approval_delay:
1710
delay = client.approval_delay
1711
client.approvals_pending += 1
1712
approval_required = True
1715
if not client.enabled:
1716
logger.info("Client %s is disabled",
1718
if self.server.use_dbus:
1720
client.Rejected("Disabled")
1723
if client.approved or not client.approval_delay:
1724
#We are approved or approval is disabled
1726
elif client.approved is None:
1727
logger.info("Client %s needs approval",
1729
if self.server.use_dbus:
1731
client.NeedApproval(
1732
client.approval_delay_milliseconds(),
1733
client.approved_by_default)
1735
logger.warning("Client %s was not approved",
1737
if self.server.use_dbus:
1739
client.Rejected("Denied")
1742
#wait until timeout or approved
1743
time = datetime.datetime.now()
1744
client.changedstate.acquire()
1745
(client.changedstate.wait
1746
(float(client.timedelta_to_milliseconds(delay)
1748
client.changedstate.release()
1749
time2 = datetime.datetime.now()
1750
if (time2 - time) >= delay:
1751
if not client.approved_by_default:
1752
logger.warning("Client %s timed out while"
1753
" waiting for approval",
1755
if self.server.use_dbus:
1757
client.Rejected("Approval timed out")
1762
delay -= time2 - time
1765
while sent_size < len(client.secret):
1767
sent = session.send(client.secret[sent_size:])
1768
except gnutls.errors.GNUTLSError as error:
1769
logger.warning("gnutls send failed",
1772
logger.debug("Sent: %d, remaining: %d",
1773
sent, len(client.secret)
1774
- (sent_size + sent))
1777
logger.info("Sending secret to %s", client.name)
1778
# bump the timeout using extended_timeout
1779
client.bump_timeout(client.extended_timeout)
1780
if self.server.use_dbus:
809
for c in self.server.clients:
810
if c.fingerprint == fpr:
814
ipc.write(u"NOTFOUND %s %s\n"
815
% (fpr, unicode(self.client_address)))
818
# Have to check if client.still_valid(), since it is
819
# possible that the client timed out while establishing
820
# the GnuTLS session.
821
if not client.still_valid():
822
ipc.write(u"INVALID %s\n" % client.name)
825
ipc.write(u"SENDING %s\n" % client.name)
827
while sent_size < len(client.secret):
828
sent = session.send(client.secret[sent_size:])
829
logger.debug(u"Sent: %d, remaining: %d",
830
sent, len(client.secret)
831
- (sent_size + sent))
1785
if approval_required:
1786
client.approvals_pending -= 1
1789
except gnutls.errors.GNUTLSError as error:
1790
logger.warning("GnuTLS bye failed",
836
1794
def peer_certificate(session):
1005
1979
def server_activate(self):
1006
1980
if self.enabled:
1007
1981
return socketserver.TCPServer.server_activate(self)
1008
1983
def enable(self):
1009
1984
self.enabled = True
1010
def add_pipe(self, pipe):
1986
def add_pipe(self, parent_pipe, proc):
1011
1987
# Call "handle_ipc" for both data and EOF events
1012
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1014
def handle_ipc(self, source, condition, file_objects={}):
1016
gobject.IO_IN: u"IN", # There is data to read.
1017
gobject.IO_OUT: u"OUT", # Data can be written (without
1019
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1020
gobject.IO_ERR: u"ERR", # Error condition.
1021
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1022
# broken, usually for pipes and
1025
conditions_string = ' | '.join(name
1027
condition_names.iteritems()
1028
if cond & condition)
1029
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1032
# Turn the pipe file descriptor into a Python file object
1033
if source not in file_objects:
1034
file_objects[source] = os.fdopen(source, u"r", 1)
1036
# Read a line from the file object
1037
cmdline = file_objects[source].readline()
1038
if not cmdline: # Empty line means end of file
1039
# close the IPC pipe
1040
file_objects[source].close()
1041
del file_objects[source]
1043
# Stop calling this function
1046
logger.debug(u"IPC command: %r", cmdline)
1048
# Parse and act on command
1049
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1051
if cmd == u"NOTFOUND":
1052
logger.warning(u"Client not found for fingerprint: %s",
1056
mandos_dbus_service.ClientNotFound(args)
1057
elif cmd == u"INVALID":
1058
for client in self.clients:
1059
if client.name == args:
1060
logger.warning(u"Client %s is invalid", args)
1066
logger.error(u"Unknown client %s is invalid", args)
1067
elif cmd == u"SENDING":
1068
for client in self.clients:
1069
if client.name == args:
1070
logger.info(u"Sending secret to %s", client.name)
1074
client.ReceivedSecret()
1077
logger.error(u"Sending secret to unknown client %s",
1080
logger.error(u"Unknown IPC command: %r", cmdline)
1082
# Keep calling this function
1988
gobject.io_add_watch(parent_pipe.fileno(),
1989
gobject.IO_IN | gobject.IO_HUP,
1990
functools.partial(self.handle_ipc,
1995
def handle_ipc(self, source, condition, parent_pipe=None,
1996
proc = None, client_object=None):
1997
# error, or the other end of multiprocessing.Pipe has closed
1998
if condition & (gobject.IO_ERR | gobject.IO_HUP):
1999
# Wait for other process to exit
2003
# Read a request from the child
2004
request = parent_pipe.recv()
2005
command = request[0]
2007
if command == 'init':
2009
address = request[2]
2011
for c in self.clients.itervalues():
2012
if c.fingerprint == fpr:
2016
logger.info("Client not found for fingerprint: %s, ad"
2017
"dress: %s", fpr, address)
2020
mandos_dbus_service.ClientNotFound(fpr,
2022
parent_pipe.send(False)
2025
gobject.io_add_watch(parent_pipe.fileno(),
2026
gobject.IO_IN | gobject.IO_HUP,
2027
functools.partial(self.handle_ipc,
2033
parent_pipe.send(True)
2034
# remove the old hook in favor of the new above hook on
2037
if command == 'funcall':
2038
funcname = request[1]
2042
parent_pipe.send(('data', getattr(client_object,
2046
if command == 'getattr':
2047
attrname = request[1]
2048
if callable(client_object.__getattribute__(attrname)):
2049
parent_pipe.send(('function',))
2051
parent_pipe.send(('data', client_object
2052
.__getattribute__(attrname)))
2054
if command == 'setattr':
2055
attrname = request[1]
2057
setattr(client_object, attrname, value)
1086
2062
def string_to_delta(interval):
1087
2063
"""Parse a string and return a datetime.timedelta
1089
>>> string_to_delta(u'7d')
2065
>>> string_to_delta('7d')
1090
2066
datetime.timedelta(7)
1091
>>> string_to_delta(u'60s')
2067
>>> string_to_delta('60s')
1092
2068
datetime.timedelta(0, 60)
1093
>>> string_to_delta(u'60m')
2069
>>> string_to_delta('60m')
1094
2070
datetime.timedelta(0, 3600)
1095
>>> string_to_delta(u'24h')
2071
>>> string_to_delta('24h')
1096
2072
datetime.timedelta(1)
1097
>>> string_to_delta(u'1w')
2073
>>> string_to_delta('1w')
1098
2074
datetime.timedelta(7)
1099
>>> string_to_delta(u'5m 30s')
2075
>>> string_to_delta('5m 30s')
1100
2076
datetime.timedelta(0, 330)
1102
2078
timevalue = datetime.timedelta(0)
1175
######################################################################
2129
##################################################################
1176
2130
# Parsing of options, both command line and config file
1178
parser = optparse.OptionParser(version = "%%prog %s" % version)
1179
parser.add_option("-i", u"--interface", type=u"string",
1180
metavar="IF", help=u"Bind to interface IF")
1181
parser.add_option("-a", u"--address", type=u"string",
1182
help=u"Address to listen for requests on")
1183
parser.add_option("-p", u"--port", type=u"int",
1184
help=u"Port number to receive requests on")
1185
parser.add_option("--check", action=u"store_true",
1186
help=u"Run self-test")
1187
parser.add_option("--debug", action=u"store_true",
1188
help=u"Debug mode; run in foreground and log to"
1190
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1191
u" priority string (see GnuTLS documentation)")
1192
parser.add_option("--servicename", type=u"string",
1193
metavar=u"NAME", help=u"Zeroconf service name")
1194
parser.add_option("--configdir", type=u"string",
1195
default=u"/etc/mandos", metavar=u"DIR",
1196
help=u"Directory to search for configuration"
1198
parser.add_option("--no-dbus", action=u"store_false",
1199
dest=u"use_dbus", help=u"Do not provide D-Bus"
1200
u" system bus interface")
1201
parser.add_option("--no-ipv6", action=u"store_false",
1202
dest=u"use_ipv6", help=u"Do not use IPv6")
1203
options = parser.parse_args()[0]
2132
parser = argparse.ArgumentParser()
2133
parser.add_argument("-v", "--version", action="version",
2134
version = "%(prog)s {0}".format(version),
2135
help="show version number and exit")
2136
parser.add_argument("-i", "--interface", metavar="IF",
2137
help="Bind to interface IF")
2138
parser.add_argument("-a", "--address",
2139
help="Address to listen for requests on")
2140
parser.add_argument("-p", "--port", type=int,
2141
help="Port number to receive requests on")
2142
parser.add_argument("--check", action="store_true",
2143
help="Run self-test")
2144
parser.add_argument("--debug", action="store_true",
2145
help="Debug mode; run in foreground and log"
2147
parser.add_argument("--debuglevel", metavar="LEVEL",
2148
help="Debug level for stdout output")
2149
parser.add_argument("--priority", help="GnuTLS"
2150
" priority string (see GnuTLS documentation)")
2151
parser.add_argument("--servicename",
2152
metavar="NAME", help="Zeroconf service name")
2153
parser.add_argument("--configdir",
2154
default="/etc/mandos", metavar="DIR",
2155
help="Directory to search for configuration"
2157
parser.add_argument("--no-dbus", action="store_false",
2158
dest="use_dbus", help="Do not provide D-Bus"
2159
" system bus interface")
2160
parser.add_argument("--no-ipv6", action="store_false",
2161
dest="use_ipv6", help="Do not use IPv6")
2162
parser.add_argument("--no-restore", action="store_false",
2163
dest="restore", help="Do not restore stored"
2165
parser.add_argument("--statedir", metavar="DIR",
2166
help="Directory to save/restore state in")
2168
options = parser.parse_args()
1205
2170
if options.check:
1253
2222
##################################################################
1255
2224
# For convenience
1256
debug = server_settings[u"debug"]
1257
use_dbus = server_settings[u"use_dbus"]
1258
use_ipv6 = server_settings[u"use_ipv6"]
1261
syslogger.setLevel(logging.WARNING)
1262
console.setLevel(logging.WARNING)
1264
if server_settings[u"servicename"] != u"Mandos":
2225
debug = server_settings["debug"]
2226
debuglevel = server_settings["debuglevel"]
2227
use_dbus = server_settings["use_dbus"]
2228
use_ipv6 = server_settings["use_ipv6"]
2229
stored_state_path = os.path.join(server_settings["statedir"],
2233
initlogger(debug, logging.DEBUG)
2238
level = getattr(logging, debuglevel.upper())
2239
initlogger(debug, level)
2241
if server_settings["servicename"] != "Mandos":
1265
2242
syslogger.setFormatter(logging.Formatter
1266
(u'Mandos (%s) [%%(process)d]:'
1267
u' %%(levelname)s: %%(message)s'
1268
% server_settings[u"servicename"]))
2243
('Mandos ({0}) [%(process)d]:'
2244
' %(levelname)s: %(message)s'
2245
.format(server_settings
1270
2248
# Parse config file with clients
1271
client_defaults = { u"timeout": u"1h",
1273
u"checker": u"fping -q -- %%(host)s",
1276
client_config = configparser.SafeConfigParser(client_defaults)
1277
client_config.read(os.path.join(server_settings[u"configdir"],
2249
client_config = configparser.SafeConfigParser(Client
2251
client_config.read(os.path.join(server_settings["configdir"],
1280
2254
global mandos_dbus_service
1281
2255
mandos_dbus_service = None
1283
tcp_server = MandosServer((server_settings[u"address"],
1284
server_settings[u"port"]),
2257
tcp_server = MandosServer((server_settings["address"],
2258
server_settings["port"]),
1286
interface=server_settings[u"interface"],
2260
interface=(server_settings["interface"]
1287
2262
use_ipv6=use_ipv6,
1288
2263
gnutls_priority=
1289
server_settings[u"priority"],
2264
server_settings["priority"],
1290
2265
use_dbus=use_dbus)
1291
pidfilename = u"/var/run/mandos.pid"
1293
pidfile = open(pidfilename, u"w")
1295
logger.error(u"Could not open file %r", pidfilename)
2267
pidfilename = "/var/run/mandos.pid"
2269
pidfile = open(pidfilename, "w")
2270
except IOError as e:
2271
logger.error("Could not open file %r", pidfilename,
1298
uid = pwd.getpwnam(u"_mandos").pw_uid
1299
gid = pwd.getpwnam(u"_mandos").pw_gid
2274
for name in ("_mandos", "mandos", "nobody"):
1302
uid = pwd.getpwnam(u"mandos").pw_uid
1303
gid = pwd.getpwnam(u"mandos").pw_gid
2276
uid = pwd.getpwnam(name).pw_uid
2277
gid = pwd.getpwnam(name).pw_gid
1304
2279
except KeyError:
1306
uid = pwd.getpwnam(u"nobody").pw_uid
1307
gid = pwd.getpwnam(u"nobody").pw_gid
1314
except OSError, error:
2287
except OSError as error:
1315
2288
if error[0] != errno.EPERM:
1318
# Enable all possible GnuTLS debugging
2292
# Enable all possible GnuTLS debugging
1320
2294
# "Use a log level over 10 to enable all debugging options."
1321
2295
# - GnuTLS manual
1322
2296
gnutls.library.functions.gnutls_global_set_log_level(11)
1324
2298
@gnutls.library.types.gnutls_log_func
1325
2299
def debug_gnutls(level, string):
1326
logger.debug(u"GnuTLS: %s", string[:-1])
2300
logger.debug("GnuTLS: %s", string[:-1])
1328
2302
(gnutls.library.functions
1329
2303
.gnutls_global_set_log_function(debug_gnutls))
2305
# Redirect stdin so all checkers get /dev/null
2306
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2307
os.dup2(null, sys.stdin.fileno())
2311
# Need to fork before connecting to D-Bus
2313
# Close all input and output, do double fork, etc.
2316
gobject.threads_init()
1331
2318
global main_loop
1332
2319
# From the Avahi example code
1333
DBusGMainLoop(set_as_default=True )
2320
DBusGMainLoop(set_as_default=True)
1334
2321
main_loop = gobject.MainLoop()
1335
2322
bus = dbus.SystemBus()
1336
2323
# End of Avahi example code
1338
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
2326
bus_name = dbus.service.BusName("se.recompile.Mandos",
2327
bus, do_not_queue=True)
2328
old_bus_name = (dbus.service.BusName
2329
("se.bsnet.fukt.Mandos", bus,
2331
except dbus.exceptions.NameExistsException as e:
2332
logger.error("Disabling D-Bus:", exc_info=e)
2334
server_settings["use_dbus"] = False
2335
tcp_server.use_dbus = False
1339
2336
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1340
service = AvahiService(name = server_settings[u"servicename"],
1341
servicetype = u"_mandos._tcp",
1342
protocol = protocol, bus = bus)
2337
service = AvahiServiceToSyslog(name =
2338
server_settings["servicename"],
2339
servicetype = "_mandos._tcp",
2340
protocol = protocol, bus = bus)
1343
2341
if server_settings["interface"]:
1344
2342
service.interface = (if_nametoindex
1345
(str(server_settings[u"interface"])))
2343
(str(server_settings["interface"])))
2345
global multiprocessing_manager
2346
multiprocessing_manager = multiprocessing.Manager()
1347
2348
client_class = Client
1349
2350
client_class = functools.partial(ClientDBus, bus = bus)
1350
tcp_server.clients.update(set(
1351
client_class(name = section,
1352
config= dict(client_config.items(section)))
1353
for section in client_config.sections()))
2352
client_settings = Client.config_parser(client_config)
2353
old_client_settings = {}
2356
# Get client data and settings from last running state.
2357
if server_settings["restore"]:
2359
with open(stored_state_path, "rb") as stored_state:
2360
clients_data, old_client_settings = (pickle.load
2362
os.remove(stored_state_path)
2363
except IOError as e:
2364
if e.errno == errno.ENOENT:
2365
logger.warning("Could not load persistent state: {0}"
2366
.format(os.strerror(e.errno)))
2368
logger.critical("Could not load persistent state:",
2371
except EOFError as e:
2372
logger.warning("Could not load persistent state: "
2373
"EOFError:", exc_info=e)
2375
with PGPEngine() as pgp:
2376
for client_name, client in clients_data.iteritems():
2377
# Decide which value to use after restoring saved state.
2378
# We have three different values: Old config file,
2379
# new config file, and saved state.
2380
# New config value takes precedence if it differs from old
2381
# config value, otherwise use saved state.
2382
for name, value in client_settings[client_name].items():
2384
# For each value in new config, check if it
2385
# differs from the old config value (Except for
2386
# the "secret" attribute)
2387
if (name != "secret" and
2388
value != old_client_settings[client_name]
2390
client[name] = value
2394
# Clients who has passed its expire date can still be
2395
# enabled if its last checker was successful. Clients
2396
# whose checker succeeded before we stored its state is
2397
# assumed to have successfully run all checkers during
2399
if client["enabled"]:
2400
if datetime.datetime.utcnow() >= client["expires"]:
2401
if not client["last_checked_ok"]:
2403
"disabling client {0} - Client never "
2404
"performed a successful checker"
2405
.format(client_name))
2406
client["enabled"] = False
2407
elif client["last_checker_status"] != 0:
2409
"disabling client {0} - Client "
2410
"last checker failed with error code {1}"
2411
.format(client_name,
2412
client["last_checker_status"]))
2413
client["enabled"] = False
2415
client["expires"] = (datetime.datetime
2417
+ client["timeout"])
2418
logger.debug("Last checker succeeded,"
2419
" keeping {0} enabled"
2420
.format(client_name))
2422
client["secret"] = (
2423
pgp.decrypt(client["encrypted_secret"],
2424
client_settings[client_name]
2427
# If decryption fails, we use secret from new settings
2428
logger.debug("Failed to decrypt {0} old secret"
2429
.format(client_name))
2430
client["secret"] = (
2431
client_settings[client_name]["secret"])
2433
# Add/remove clients based on new changes made to config
2434
for client_name in (set(old_client_settings)
2435
- set(client_settings)):
2436
del clients_data[client_name]
2437
for client_name in (set(client_settings)
2438
- set(old_client_settings)):
2439
clients_data[client_name] = client_settings[client_name]
2441
# Create all client objects
2442
for client_name, client in clients_data.iteritems():
2443
tcp_server.clients[client_name] = client_class(
2444
name = client_name, settings = client)
1354
2446
if not tcp_server.clients:
1355
logger.warning(u"No clients defined")
1358
# Redirect stdin so all checkers get /dev/null
1359
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1360
os.dup2(null, sys.stdin.fileno())
1364
# No console logging
1365
logger.removeHandler(console)
1366
# Close all input and output, do double fork, etc.
1370
with closing(pidfile):
1372
pidfile.write(str(pid) + "\n")
1375
logger.error(u"Could not write to file %r with PID %d",
1378
# "pidfile" was never created
1383
"Cleanup function; run on exit"
1386
while tcp_server.clients:
1387
client = tcp_server.clients.pop()
1388
client.disable_hook = None
1391
atexit.register(cleanup)
2447
logger.warning("No clients defined")
2453
pidfile.write(str(pid) + "\n".encode("utf-8"))
2456
logger.error("Could not write to file %r with PID %d",
2459
# "pidfile" was never created
1394
2462
signal.signal(signal.SIGINT, signal.SIG_IGN)
1395
2464
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1396
2465
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1399
class MandosDBusService(dbus.service.Object):
2468
@alternate_dbus_interfaces({"se.recompile.Mandos":
2469
"se.bsnet.fukt.Mandos"})
2470
class MandosDBusService(DBusObjectWithProperties):
1400
2471
"""A D-Bus proxy object"""
1401
2472
def __init__(self):
1402
dbus.service.Object.__init__(self, bus, u"/")
1403
_interface = u"se.bsnet.fukt.Mandos"
1405
@dbus.service.signal(_interface, signature=u"oa{sv}")
1406
def ClientAdded(self, objpath, properties):
1410
@dbus.service.signal(_interface, signature=u"s")
1411
def ClientNotFound(self, fingerprint):
1415
@dbus.service.signal(_interface, signature=u"os")
2473
dbus.service.Object.__init__(self, bus, "/")
2474
_interface = "se.recompile.Mandos"
2476
@dbus_interface_annotations(_interface)
2478
return { "org.freedesktop.DBus.Property"
2479
".EmitsChangedSignal":
2482
@dbus.service.signal(_interface, signature="o")
2483
def ClientAdded(self, objpath):
2487
@dbus.service.signal(_interface, signature="ss")
2488
def ClientNotFound(self, fingerprint, address):
2492
@dbus.service.signal(_interface, signature="os")
1416
2493
def ClientRemoved(self, objpath, name):
1420
@dbus.service.method(_interface, out_signature=u"ao")
2497
@dbus.service.method(_interface, out_signature="ao")
1421
2498
def GetAllClients(self):
1423
2500
return dbus.Array(c.dbus_object_path
1424
for c in tcp_server.clients)
2502
tcp_server.clients.itervalues())
1426
2504
@dbus.service.method(_interface,
1427
out_signature=u"a{oa{sv}}")
2505
out_signature="a{oa{sv}}")
1428
2506
def GetAllClientsWithProperties(self):
1430
2508
return dbus.Dictionary(
1431
((c.dbus_object_path, c.GetAllProperties())
1432
for c in tcp_server.clients),
1433
signature=u"oa{sv}")
2509
((c.dbus_object_path, c.GetAll(""))
2510
for c in tcp_server.clients.itervalues()),
1435
@dbus.service.method(_interface, in_signature=u"o")
2513
@dbus.service.method(_interface, in_signature="o")
1436
2514
def RemoveClient(self, object_path):
1438
for c in tcp_server.clients:
2516
for c in tcp_server.clients.itervalues():
1439
2517
if c.dbus_object_path == object_path:
1440
tcp_server.clients.remove(c)
2518
del tcp_server.clients[c.name]
1441
2519
c.remove_from_connection()
1442
2520
# Don't signal anything except ClientRemoved
1443
c.disable(signal=False)
2521
c.disable(quiet=True)
1444
2522
# Emit D-Bus signal
1445
2523
self.ClientRemoved(object_path, c.name)
2525
raise KeyError(object_path)
1451
2529
mandos_dbus_service = MandosDBusService()
1453
for client in tcp_server.clients:
2532
"Cleanup function; run on exit"
2535
multiprocessing.active_children()
2536
if not (tcp_server.clients or client_settings):
2539
# Store client before exiting. Secrets are encrypted with key
2540
# based on what config file has. If config file is
2541
# removed/edited, old secret will thus be unrecovable.
2543
with PGPEngine() as pgp:
2544
for client in tcp_server.clients.itervalues():
2545
key = client_settings[client.name]["secret"]
2546
client.encrypted_secret = pgp.encrypt(client.secret,
2550
# A list of attributes that can not be pickled
2552
exclude = set(("bus", "changedstate", "secret",
2554
for name, typ in (inspect.getmembers
2555
(dbus.service.Object)):
2558
client_dict["encrypted_secret"] = (client
2560
for attr in client.client_structure:
2561
if attr not in exclude:
2562
client_dict[attr] = getattr(client, attr)
2564
clients[client.name] = client_dict
2565
del client_settings[client.name]["secret"]
2568
with (tempfile.NamedTemporaryFile
2569
(mode='wb', suffix=".pickle", prefix='clients-',
2570
dir=os.path.dirname(stored_state_path),
2571
delete=False)) as stored_state:
2572
pickle.dump((clients, client_settings), stored_state)
2573
tempname=stored_state.name
2574
os.rename(tempname, stored_state_path)
2575
except (IOError, OSError) as e:
2581
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2582
logger.warning("Could not save persistent state: {0}"
2583
.format(os.strerror(e.errno)))
2585
logger.warning("Could not save persistent state:",
2589
# Delete all clients, and settings from config
2590
while tcp_server.clients:
2591
name, client = tcp_server.clients.popitem()
2593
client.remove_from_connection()
2594
# Don't signal anything except ClientRemoved
2595
client.disable(quiet=True)
2598
mandos_dbus_service.ClientRemoved(client
2601
client_settings.clear()
2603
atexit.register(cleanup)
2605
for client in tcp_server.clients.itervalues():
1455
2607
# Emit D-Bus signal
1456
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1457
client.GetAllProperties())
2608
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2609
# Need to initiate checking of clients
2611
client.init_checker()
1460
2613
tcp_server.enable()
1461
2614
tcp_server.server_activate()