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(b"16s16x", interface))
108
interface_index = struct.unpack("I", ifreq[16:20])[0]
109
return interface_index
112
def initlogger(debug, level=logging.WARNING):
113
"""init logger and add loglevel"""
116
syslogger = (logging.handlers.SysLogHandler
118
logging.handlers.SysLogHandler.LOG_DAEMON,
119
address = "/dev/log"))
120
syslogger.setFormatter(logging.Formatter
121
('Mandos [%(process)d]: %(levelname)s:'
123
logger.addHandler(syslogger)
126
console = logging.StreamHandler()
127
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
131
logger.addHandler(console)
132
logger.setLevel(level)
135
class PGPError(Exception):
136
"""Exception if encryption/decryption fails"""
140
class PGPEngine(object):
141
"""A simple class for OpenPGP symmetric encryption & decryption"""
143
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
144
self.gnupgargs = ['--batch',
145
'--home', self.tempdir,
153
def __exit__(self, exc_type, exc_value, traceback):
161
if self.tempdir is not None:
162
# Delete contents of tempdir
163
for root, dirs, files in os.walk(self.tempdir,
165
for filename in files:
166
os.remove(os.path.join(root, filename))
168
os.rmdir(os.path.join(root, dirname))
170
os.rmdir(self.tempdir)
173
def password_encode(self, password):
174
# Passphrase can not be empty and can not contain newlines or
175
# NUL bytes. So we prefix it and hex encode it.
176
encoded = b"mandos" + binascii.hexlify(password)
177
if len(encoded) > 2048:
178
# GnuPG can't handle long passwords, so encode differently
179
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
180
.replace(b"\n", b"\\n")
181
.replace(b"\0", b"\\x00"))
184
def encrypt(self, data, password):
185
passphrase = self.password_encode(password)
186
with tempfile.NamedTemporaryFile(dir=self.tempdir
188
passfile.write(passphrase)
190
proc = subprocess.Popen(['gpg', '--symmetric',
194
stdin = subprocess.PIPE,
195
stdout = subprocess.PIPE,
196
stderr = subprocess.PIPE)
197
ciphertext, err = proc.communicate(input = data)
198
if proc.returncode != 0:
202
def decrypt(self, data, password):
203
passphrase = self.password_encode(password)
204
with tempfile.NamedTemporaryFile(dir = self.tempdir
206
passfile.write(passphrase)
208
proc = subprocess.Popen(['gpg', '--decrypt',
212
stdin = subprocess.PIPE,
213
stdout = subprocess.PIPE,
214
stderr = subprocess.PIPE)
215
decrypted_plaintext, err = proc.communicate(input
217
if proc.returncode != 0:
219
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)
222
102
class AvahiError(Exception):
223
103
def __init__(self, value, *args, **kwargs):
224
104
self.value = value
225
return super(AvahiError, self).__init__(value, *args,
105
super(AvahiError, self).__init__(value, *args, **kwargs)
106
def __unicode__(self):
107
return unicode(repr(self.value))
228
109
class AvahiServiceError(AvahiError):
322
197
dbus.UInt16(self.port),
323
198
avahi.string_array_to_txt_array(self.TXT))
324
199
self.group.Commit()
326
200
def entry_group_state_changed(self, state, error):
327
201
"""Derived from the Avahi example code"""
328
logger.debug("Avahi entry group state change: %i", state)
202
logger.debug(u"Avahi entry group state change: %i", state)
330
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
331
logger.debug("Zeroconf service established.")
205
logger.debug(u"Zeroconf service established.")
332
206
elif state == avahi.ENTRY_GROUP_COLLISION:
333
logger.info("Zeroconf service name collision.")
207
logger.warning(u"Zeroconf service name collision.")
335
209
elif state == avahi.ENTRY_GROUP_FAILURE:
336
logger.critical("Avahi: Error in group state changed %s",
210
logger.critical(u"Avahi: Error in group state changed %s",
338
raise AvahiGroupError("State changed: {!s}"
212
raise AvahiGroupError(u"State changed: %s"
341
214
def cleanup(self):
342
215
"""Derived from the Avahi example code"""
343
216
if self.group is not None:
346
except (dbus.exceptions.UnknownMethodException,
347
dbus.exceptions.DBusException):
349
218
self.group = None
352
def server_state_changed(self, state, error=None):
219
def server_state_changed(self, state):
353
220
"""Derived from the Avahi example code"""
354
logger.debug("Avahi server state change: %i", state)
355
bad_states = { avahi.SERVER_INVALID:
356
"Zeroconf server invalid",
357
avahi.SERVER_REGISTERING: None,
358
avahi.SERVER_COLLISION:
359
"Zeroconf server name collision",
360
avahi.SERVER_FAILURE:
361
"Zeroconf server failure" }
362
if state in bad_states:
363
if bad_states[state] is not None:
365
logger.error(bad_states[state])
367
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")
369
225
elif state == avahi.SERVER_RUNNING:
373
logger.debug("Unknown state: %r", state)
375
logger.debug("Unknown state: %r: %r", state, error)
377
227
def activate(self):
378
228
"""Derived from the Avahi example code"""
379
229
if self.server is None:
380
230
self.server = dbus.Interface(
381
231
self.bus.get_object(avahi.DBUS_NAME,
382
avahi.DBUS_PATH_SERVER,
383
follow_name_owner_changes=True),
232
avahi.DBUS_PATH_SERVER),
384
233
avahi.DBUS_INTERFACE_SERVER)
385
self.server.connect_to_signal("StateChanged",
234
self.server.connect_to_signal(u"StateChanged",
386
235
self.server_state_changed)
387
236
self.server_state_changed(self.server.GetState())
390
class AvahiServiceToSyslog(AvahiService):
392
"""Add the new name to the syslog messages"""
393
ret = AvahiService.rename(self)
394
syslogger.setFormatter(logging.Formatter
395
('Mandos ({}) [%(process)d]:'
396
' %(levelname)s: %(message)s'
401
239
class Client(object):
402
240
"""A representation of a client host served by this server.
405
approved: bool(); 'None' if not yet approved/disapproved
243
_approved: bool(); 'None' if not yet approved/disapproved
406
244
approval_delay: datetime.timedelta(); Time to wait for approval
407
245
approval_duration: datetime.timedelta(); Duration of one approval
408
246
checker: subprocess.Popen(); a running checker process used
409
247
to see if the client lives.
410
248
'None' if no process is running.
411
checker_callback_tag: a gobject event source tag, or None
249
checker_callback_tag: - '' -
412
250
checker_command: string; External command which is run to check
413
251
if client lives. %() expansions are done at
414
252
runtime with vars(self) as dict, so that for
415
253
instance %(name)s can be used in the command.
416
254
checker_initiator_tag: a gobject event source tag, or None
417
255
created: datetime.datetime(); (UTC) object creation
418
client_structure: Object describing what attributes a client has
419
and is used for storing the client at exit
420
256
current_checker_command: string; current running checker_command
421
disable_initiator_tag: a gobject event source tag, or None
257
disable_hook: If set, called by disable() as disable_hook(self)
258
disable_initiator_tag: - '' -
423
260
fingerprint: string (40 or 32 hexadecimal digits); used to
424
261
uniquely identify the client
425
262
host: string; available for use by the checker command
426
263
interval: datetime.timedelta(); How often to start a new checker
427
last_approval_request: datetime.datetime(); (UTC) or None
428
264
last_checked_ok: datetime.datetime(); (UTC) or None
429
last_checker_status: integer between 0 and 255 reflecting exit
430
status of last checker. -1 reflects crashed
431
checker, -2 means no checker completed yet.
432
last_enabled: datetime.datetime(); (UTC) or None
265
last_enabled: datetime.datetime(); (UTC)
433
266
name: string; from the config file, used in log messages and
434
267
D-Bus identifiers
435
268
secret: bytestring; sent verbatim (over TLS) to client
436
269
timeout: datetime.timedelta(); How long from last_checked_ok
437
270
until this client is disabled
438
extended_timeout: extra long timeout when secret has been sent
439
271
runtime_expansions: Allowed attributes for runtime expansion.
440
expires: datetime.datetime(); time (UTC) when a client will be
442
server_settings: The server_settings dict from main()
445
runtime_expansions = ("approval_delay", "approval_duration",
446
"created", "enabled", "expires",
447
"fingerprint", "host", "interval",
448
"last_approval_request", "last_checked_ok",
449
"last_enabled", "name", "timeout")
450
client_defaults = { "timeout": "PT5M",
451
"extended_timeout": "PT15M",
453
"checker": "fping -q -- %%(host)s",
455
"approval_delay": "PT0S",
456
"approval_duration": "PT1S",
457
"approved_by_default": "True",
274
runtime_expansions = (u"approval_delay", u"approval_duration",
275
u"created", u"enabled", u"fingerprint",
276
u"host", u"interval", u"last_checked_ok",
277
u"last_enabled", u"name", u"timeout")
462
def config_parser(config):
463
"""Construct a new dict of client settings of this form:
464
{ client_name: {setting_name: value, ...}, ...}
465
with exceptions for any special settings as defined above.
466
NOTE: Must be a pure function. Must return the same result
467
value given the same arguments.
470
for client_name in config.sections():
471
section = dict(config.items(client_name))
472
client = settings[client_name] = {}
474
client["host"] = section["host"]
475
# Reformat values from string types to Python types
476
client["approved_by_default"] = config.getboolean(
477
client_name, "approved_by_default")
478
client["enabled"] = config.getboolean(client_name,
481
client["fingerprint"] = (section["fingerprint"].upper()
483
if "secret" in section:
484
client["secret"] = section["secret"].decode("base64")
485
elif "secfile" in section:
486
with open(os.path.expanduser(os.path.expandvars
487
(section["secfile"])),
489
client["secret"] = secfile.read()
491
raise TypeError("No secret or secfile for section {}"
493
client["timeout"] = string_to_delta(section["timeout"])
494
client["extended_timeout"] = string_to_delta(
495
section["extended_timeout"])
496
client["interval"] = string_to_delta(section["interval"])
497
client["approval_delay"] = string_to_delta(
498
section["approval_delay"])
499
client["approval_duration"] = string_to_delta(
500
section["approval_duration"])
501
client["checker_command"] = section["checker"]
502
client["last_approval_request"] = None
503
client["last_checked_ok"] = None
504
client["last_checker_status"] = -2
508
def __init__(self, settings, name = None, server_settings=None):
280
def _timedelta_to_milliseconds(td):
281
"Convert a datetime.timedelta() to milliseconds"
282
return ((td.days * 24 * 60 * 60 * 1000)
283
+ (td.seconds * 1000)
284
+ (td.microseconds // 1000))
286
def timeout_milliseconds(self):
287
"Return the 'timeout' attribute in milliseconds"
288
return self._timedelta_to_milliseconds(self.timeout)
290
def interval_milliseconds(self):
291
"Return the 'interval' attribute in milliseconds"
292
return self._timedelta_to_milliseconds(self.interval)
294
def approval_delay_milliseconds(self):
295
return self._timedelta_to_milliseconds(self.approval_delay)
297
def __init__(self, name = None, disable_hook=None, config=None):
298
"""Note: the 'checker' key in 'config' sets the
299
'checker_command' attribute and *not* the 'checker'
510
if server_settings is None:
512
self.server_settings = server_settings
513
# adding all client settings
514
for setting, value in settings.items():
515
setattr(self, setting, value)
518
if not hasattr(self, "last_enabled"):
519
self.last_enabled = datetime.datetime.utcnow()
520
if not hasattr(self, "expires"):
521
self.expires = (datetime.datetime.utcnow()
524
self.last_enabled = None
527
logger.debug("Creating client %r", self.name)
304
logger.debug(u"Creating client %r", self.name)
528
305
# Uppercase and remove spaces from fingerprint for later
529
306
# comparison purposes with return value from the fingerprint()
531
logger.debug(" Fingerprint: %s", self.fingerprint)
532
self.created = settings.get("created",
533
datetime.datetime.utcnow())
535
# attributes specific for this server instance
308
self.fingerprint = (config[u"fingerprint"].upper()
310
logger.debug(u" Fingerprint: %s", self.fingerprint)
311
if u"secret" in config:
312
self.secret = config[u"secret"].decode(u"base64")
313
elif u"secfile" in config:
314
with open(os.path.expanduser(os.path.expandvars
315
(config[u"secfile"])),
317
self.secret = secfile.read()
319
raise TypeError(u"No secret or secfile for client %s"
321
self.host = config.get(u"host", u"")
322
self.created = datetime.datetime.utcnow()
324
self.last_enabled = None
325
self.last_checked_ok = None
326
self.timeout = string_to_delta(config[u"timeout"])
327
self.interval = string_to_delta(config[u"interval"])
328
self.disable_hook = disable_hook
536
329
self.checker = None
537
330
self.checker_initiator_tag = None
538
331
self.disable_initiator_tag = None
539
332
self.checker_callback_tag = None
333
self.checker_command = config[u"checker"]
540
334
self.current_checker_command = None
335
self.last_connect = None
336
self._approved = None
337
self.approved_by_default = config.get(u"approved_by_default",
542
339
self.approvals_pending = 0
543
self.changedstate = (multiprocessing_manager
544
.Condition(multiprocessing_manager
546
self.client_structure = [attr for attr in
547
self.__dict__.iterkeys()
548
if not attr.startswith("_")]
549
self.client_structure.append("client_structure")
551
for name, t in inspect.getmembers(type(self),
555
if not name.startswith("_"):
556
self.client_structure.append(name)
340
self.approval_delay = string_to_delta(
341
config[u"approval_delay"])
342
self.approval_duration = string_to_delta(
343
config[u"approval_duration"])
344
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
558
# Send notice to process children that client state has changed
559
346
def send_changedstate(self):
560
with self.changedstate:
561
self.changedstate.notify_all()
347
self.changedstate.acquire()
348
self.changedstate.notify_all()
349
self.changedstate.release()
563
351
def enable(self):
564
352
"""Start this client's checker and timeout hooks"""
565
if getattr(self, "enabled", False):
353
if getattr(self, u"enabled", False):
566
354
# Already enabled
568
self.expires = datetime.datetime.utcnow() + self.timeout
356
self.send_changedstate()
570
357
self.last_enabled = datetime.datetime.utcnow()
572
self.send_changedstate()
358
# Schedule a new checker to be started an 'interval' from now,
359
# and every interval from then on.
360
self.checker_initiator_tag = (gobject.timeout_add
361
(self.interval_milliseconds(),
363
# Schedule a disable() when 'timeout' has passed
364
self.disable_initiator_tag = (gobject.timeout_add
365
(self.timeout_milliseconds(),
368
# Also start a new checker *right now*.
574
371
def disable(self, quiet=True):
575
372
"""Disable this client."""
576
373
if not getattr(self, "enabled", False):
579
logger.info("Disabling client %s", self.name)
580
if getattr(self, "disable_initiator_tag", None) is not None:
376
self.send_changedstate()
378
logger.info(u"Disabling client %s", self.name)
379
if getattr(self, u"disable_initiator_tag", False):
581
380
gobject.source_remove(self.disable_initiator_tag)
582
381
self.disable_initiator_tag = None
584
if getattr(self, "checker_initiator_tag", None) is not None:
382
if getattr(self, u"checker_initiator_tag", False):
585
383
gobject.source_remove(self.checker_initiator_tag)
586
384
self.checker_initiator_tag = None
587
385
self.stop_checker()
386
if self.disable_hook:
387
self.disable_hook(self)
588
388
self.enabled = False
590
self.send_changedstate()
591
389
# Do not run this again if called by a gobject.timeout_add
594
392
def __del__(self):
393
self.disable_hook = None
597
def init_checker(self):
598
# Schedule a new checker to be started an 'interval' from now,
599
# and every interval from then on.
600
if self.checker_initiator_tag is not None:
601
gobject.source_remove(self.checker_initiator_tag)
602
self.checker_initiator_tag = (gobject.timeout_add
604
.total_seconds() * 1000),
606
# Schedule a disable() when 'timeout' has passed
607
if self.disable_initiator_tag is not None:
608
gobject.source_remove(self.disable_initiator_tag)
609
self.disable_initiator_tag = (gobject.timeout_add
611
.total_seconds() * 1000),
613
# Also start a new checker *right now*.
616
396
def checker_callback(self, pid, condition, command):
617
397
"""The checker has completed, so take appropriate actions."""
618
398
self.checker_callback_tag = None
619
399
self.checker = None
620
400
if os.WIFEXITED(condition):
621
self.last_checker_status = os.WEXITSTATUS(condition)
622
if self.last_checker_status == 0:
623
logger.info("Checker for %(name)s succeeded",
401
exitstatus = os.WEXITSTATUS(condition)
403
logger.info(u"Checker for %(name)s succeeded",
625
405
self.checked_ok()
627
logger.info("Checker for %(name)s failed",
407
logger.info(u"Checker for %(name)s failed",
630
self.last_checker_status = -1
631
logger.warning("Checker for %(name)s crashed?",
410
logger.warning(u"Checker for %(name)s crashed?",
634
413
def checked_ok(self):
635
"""Assert that the client has been seen, alive and well."""
414
"""Bump up the timeout for this client.
416
This should only be called when the client has been seen,
636
419
self.last_checked_ok = datetime.datetime.utcnow()
637
self.last_checker_status = 0
640
def bump_timeout(self, timeout=None):
641
"""Bump up the timeout for this client."""
643
timeout = self.timeout
644
if self.disable_initiator_tag is not None:
645
gobject.source_remove(self.disable_initiator_tag)
646
self.disable_initiator_tag = None
647
if getattr(self, "enabled", False):
648
self.disable_initiator_tag = (gobject.timeout_add
649
(int(timeout.total_seconds()
650
* 1000), self.disable))
651
self.expires = datetime.datetime.utcnow() + timeout
653
def need_approval(self):
654
self.last_approval_request = datetime.datetime.utcnow()
420
gobject.source_remove(self.disable_initiator_tag)
421
self.disable_initiator_tag = (gobject.timeout_add
422
(self.timeout_milliseconds(),
656
425
def start_checker(self):
657
426
"""Start a new checker subprocess if one is not running.
670
439
# If a checker exists, make sure it is not a zombie
672
441
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
673
except AttributeError:
675
except OSError as error:
676
if error.errno != errno.ECHILD:
442
except (AttributeError, OSError), error:
443
if (isinstance(error, OSError)
444
and error.errno != errno.ECHILD):
680
logger.warning("Checker was a zombie")
448
logger.warning(u"Checker was a zombie")
681
449
gobject.source_remove(self.checker_callback_tag)
682
450
self.checker_callback(pid, status,
683
451
self.current_checker_command)
684
452
# Start a new checker if needed
685
453
if self.checker is None:
686
# Escape attributes for the shell
687
escaped_attrs = { attr:
688
re.escape(unicode(getattr(self,
690
for attr in self.runtime_expansions }
692
command = self.checker_command % escaped_attrs
693
except TypeError as error:
694
logger.error('Could not format string "%s"',
695
self.checker_command, exc_info=error)
696
return True # Try again later
455
# In case checker_command has exactly one % operator
456
command = self.checker_command % self.host
458
# Escape attributes for the shell
459
escaped_attrs = dict(
461
re.escape(unicode(str(getattr(self, attr, u"")),
465
self.runtime_expansions)
468
command = self.checker_command % escaped_attrs
469
except TypeError, error:
470
logger.error(u'Could not format string "%s":'
471
u' %s', self.checker_command, error)
472
return True # Try again later
697
473
self.current_checker_command = command
699
logger.info("Starting checker %r for %s",
475
logger.info(u"Starting checker %r for %s",
700
476
command, self.name)
701
477
# We don't need to redirect stdout and stderr, since
702
478
# in normal mode, that is already done by daemon(),
703
479
# and in debug mode we don't want to. (Stdin is
704
480
# always replaced by /dev/null.)
705
# The exception is when not debugging but nevertheless
706
# running in the foreground; use the previously
709
if (not self.server_settings["debug"]
710
and self.server_settings["foreground"]):
711
popen_args.update({"stdout": wnull,
713
481
self.checker = subprocess.Popen(command,
717
except OSError as error:
718
logger.error("Failed to start subprocess",
721
self.checker_callback_tag = (gobject.child_watch_add
723
self.checker_callback,
725
# The checker may have completed before the gobject
726
# watch was added. Check for this.
483
shell=True, cwd=u"/")
484
self.checker_callback_tag = (gobject.child_watch_add
486
self.checker_callback,
488
# The checker may have completed before the gobject
489
# watch was added. Check for this.
728
490
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
729
except OSError as error:
730
if error.errno == errno.ECHILD:
731
# This should never happen
732
logger.error("Child process vanished",
737
gobject.source_remove(self.checker_callback_tag)
738
self.checker_callback(pid, status, command)
492
gobject.source_remove(self.checker_callback_tag)
493
self.checker_callback(pid, status, command)
494
except OSError, error:
495
logger.error(u"Failed to start subprocess: %s",
739
497
# Re-run this periodically if run by gobject.timeout_add
845
567
class DBusObjectWithProperties(dbus.service.Object):
846
568
"""A D-Bus object with properties.
848
570
Classes inheriting from this can use the dbus_service_property
849
571
decorator to expose methods as D-Bus properties. It exposes the
850
572
standard Get(), Set(), and GetAll() methods on the D-Bus.
854
def _is_dbus_thing(thing):
855
"""Returns a function testing if an attribute is a D-Bus thing
857
If called like _is_dbus_thing("method") it returns a function
858
suitable for use as predicate to inspect.getmembers().
860
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
576
def _is_dbus_property(obj):
577
return getattr(obj, u"_dbus_is_property", False)
863
def _get_all_dbus_things(self, thing):
579
def _get_all_dbus_properties(self):
864
580
"""Returns a generator of (name, attribute) pairs
866
return ((getattr(athing.__get__(self), "_dbus_name",
868
athing.__get__(self))
869
for cls in self.__class__.__mro__
871
inspect.getmembers(cls,
872
self._is_dbus_thing(thing)))
582
return ((prop._dbus_name, prop)
584
inspect.getmembers(self, self._is_dbus_property))
874
586
def _get_dbus_property(self, interface_name, property_name):
875
587
"""Returns a bound method if one exists which is a D-Bus
876
588
property with the specified name and interface.
878
for cls in self.__class__.__mro__:
879
for name, value in (inspect.getmembers
881
self._is_dbus_thing("property"))):
882
if (value._dbus_name == property_name
883
and value._dbus_interface == interface_name):
884
return value.__get__(self)
590
for name in (property_name,
591
property_name + u"_dbus_property"):
592
prop = getattr(self, name, None)
594
or not self._is_dbus_property(prop)
595
or prop._dbus_name != property_name
596
or (interface_name and prop._dbus_interface
597
and interface_name != prop._dbus_interface)):
886
600
# No such property
887
raise DBusPropertyNotFound(self.dbus_object_path + ":"
888
+ interface_name + "."
601
raise DBusPropertyNotFound(self.dbus_object_path + u":"
602
+ interface_name + u"."
891
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
605
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
893
607
def Get(self, interface_name, property_name):
894
608
"""Standard D-Bus property Get() method, see D-Bus standard.
896
610
prop = self._get_dbus_property(interface_name, property_name)
897
if prop._dbus_access == "write":
611
if prop._dbus_access == u"write":
898
612
raise DBusPropertyAccessException(property_name)
900
if not hasattr(value, "variant_level"):
614
if not hasattr(value, u"variant_level"):
902
616
return type(value)(value, variant_level=value.variant_level+1)
904
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
618
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
905
619
def Set(self, interface_name, property_name, value):
906
620
"""Standard D-Bus property Set() method, see D-Bus standard.
908
622
prop = self._get_dbus_property(interface_name, property_name)
909
if prop._dbus_access == "read":
623
if prop._dbus_access == u"read":
910
624
raise DBusPropertyAccessException(property_name)
911
if prop._dbus_get_args_options["byte_arrays"]:
625
if prop._dbus_get_args_options[u"byte_arrays"]:
912
626
# The byte_arrays option is not supported yet on
913
627
# signatures other than "ay".
914
if prop._dbus_signature != "ay":
915
raise ValueError("Byte arrays not supported for non-"
916
"'ay' signature {!r}"
917
.format(prop._dbus_signature))
918
value = dbus.ByteArray(b''.join(chr(byte)
628
if prop._dbus_signature != u"ay":
630
value = dbus.ByteArray(''.join(unichr(byte)
922
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
923
out_signature="a{sv}")
634
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
635
out_signature=u"a{sv}")
924
636
def GetAll(self, interface_name):
925
637
"""Standard D-Bus property GetAll() method, see D-Bus
928
640
Note: Will not include properties with access="write".
931
for name, prop in self._get_all_dbus_things("property"):
643
for name, prop in self._get_all_dbus_properties():
932
644
if (interface_name
933
645
and interface_name != prop._dbus_interface):
934
646
# Interface non-empty but did not match
936
648
# Ignore write-only properties
937
if prop._dbus_access == "write":
649
if prop._dbus_access == u"write":
940
if not hasattr(value, "variant_level"):
941
properties[name] = value
652
if not hasattr(value, u"variant_level"):
943
properties[name] = type(value)(value, variant_level=
944
value.variant_level+1)
945
return dbus.Dictionary(properties, signature="sv")
655
all[name] = type(value)(value, variant_level=
656
value.variant_level+1)
657
return dbus.Dictionary(all, signature=u"sv")
947
659
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
949
661
path_keyword='object_path',
950
662
connection_keyword='connection')
951
663
def Introspect(self, object_path, connection):
952
"""Overloading of standard D-Bus method.
954
Inserts property tags and interface annotation tags.
664
"""Standard D-Bus method, overloaded to insert property tags.
956
666
xmlstring = dbus.service.Object.Introspect(self, object_path,
959
669
document = xml.dom.minidom.parseString(xmlstring)
960
670
def make_tag(document, name, prop):
961
e = document.createElement("property")
962
e.setAttribute("name", name)
963
e.setAttribute("type", prop._dbus_signature)
964
e.setAttribute("access", prop._dbus_access)
671
e = document.createElement(u"property")
672
e.setAttribute(u"name", name)
673
e.setAttribute(u"type", prop._dbus_signature)
674
e.setAttribute(u"access", prop._dbus_access)
966
for if_tag in document.getElementsByTagName("interface"):
676
for if_tag in document.getElementsByTagName(u"interface"):
968
677
for tag in (make_tag(document, name, prop)
970
in self._get_all_dbus_things("property")
679
in self._get_all_dbus_properties()
971
680
if prop._dbus_interface
972
== if_tag.getAttribute("name")):
681
== if_tag.getAttribute(u"name")):
973
682
if_tag.appendChild(tag)
974
# Add annotation tags
975
for typ in ("method", "signal", "property"):
976
for tag in if_tag.getElementsByTagName(typ):
978
for name, prop in (self.
979
_get_all_dbus_things(typ)):
980
if (name == tag.getAttribute("name")
981
and prop._dbus_interface
982
== if_tag.getAttribute("name")):
983
annots.update(getattr
987
for name, value in annots.items():
988
ann_tag = document.createElement(
990
ann_tag.setAttribute("name", name)
991
ann_tag.setAttribute("value", value)
992
tag.appendChild(ann_tag)
993
# Add interface annotation tags
994
for annotation, value in dict(
995
itertools.chain.from_iterable(
996
annotations().items()
997
for name, annotations in
998
self._get_all_dbus_things("interface")
999
if name == if_tag.getAttribute("name")
1001
ann_tag = document.createElement("annotation")
1002
ann_tag.setAttribute("name", annotation)
1003
ann_tag.setAttribute("value", value)
1004
if_tag.appendChild(ann_tag)
1005
683
# Add the names to the return values for the
1006
684
# "org.freedesktop.DBus.Properties" methods
1007
if (if_tag.getAttribute("name")
1008
== "org.freedesktop.DBus.Properties"):
1009
for cn in if_tag.getElementsByTagName("method"):
1010
if cn.getAttribute("name") == "Get":
1011
for arg in cn.getElementsByTagName("arg"):
1012
if (arg.getAttribute("direction")
1014
arg.setAttribute("name", "value")
1015
elif cn.getAttribute("name") == "GetAll":
1016
for arg in cn.getElementsByTagName("arg"):
1017
if (arg.getAttribute("direction")
1019
arg.setAttribute("name", "props")
1020
xmlstring = document.toxml("utf-8")
685
if (if_tag.getAttribute(u"name")
686
== u"org.freedesktop.DBus.Properties"):
687
for cn in if_tag.getElementsByTagName(u"method"):
688
if cn.getAttribute(u"name") == u"Get":
689
for arg in cn.getElementsByTagName(u"arg"):
690
if (arg.getAttribute(u"direction")
692
arg.setAttribute(u"name", u"value")
693
elif cn.getAttribute(u"name") == u"GetAll":
694
for arg in cn.getElementsByTagName(u"arg"):
695
if (arg.getAttribute(u"direction")
697
arg.setAttribute(u"name", u"props")
698
xmlstring = document.toxml(u"utf-8")
1021
699
document.unlink()
1022
700
except (AttributeError, xml.dom.DOMException,
1023
xml.parsers.expat.ExpatError) as error:
1024
logger.error("Failed to override Introspection method",
701
xml.parsers.expat.ExpatError), error:
702
logger.error(u"Failed to override Introspection method",
1026
704
return xmlstring
1029
def datetime_to_dbus(dt, variant_level=0):
1030
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1032
return dbus.String("", variant_level = variant_level)
1033
return dbus.String(dt.isoformat(),
1034
variant_level=variant_level)
1037
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1038
"""A class decorator; applied to a subclass of
1039
dbus.service.Object, it will add alternate D-Bus attributes with
1040
interface names according to the "alt_interface_names" mapping.
1043
@alternate_dbus_interfaces({"org.example.Interface":
1044
"net.example.AlternateInterface"})
1045
class SampleDBusObject(dbus.service.Object):
1046
@dbus.service.method("org.example.Interface")
1047
def SampleDBusMethod():
1050
The above "SampleDBusMethod" on "SampleDBusObject" will be
1051
reachable via two interfaces: "org.example.Interface" and
1052
"net.example.AlternateInterface", the latter of which will have
1053
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1054
"true", unless "deprecate" is passed with a False value.
1056
This works for methods and signals, and also for D-Bus properties
1057
(from DBusObjectWithProperties) and interfaces (from the
1058
dbus_interface_annotations decorator).
1061
for orig_interface_name, alt_interface_name in (
1062
alt_interface_names.items()):
1064
interface_names = set()
1065
# Go though all attributes of the class
1066
for attrname, attribute in inspect.getmembers(cls):
1067
# Ignore non-D-Bus attributes, and D-Bus attributes
1068
# with the wrong interface name
1069
if (not hasattr(attribute, "_dbus_interface")
1070
or not attribute._dbus_interface
1071
.startswith(orig_interface_name)):
1073
# Create an alternate D-Bus interface name based on
1075
alt_interface = (attribute._dbus_interface
1076
.replace(orig_interface_name,
1077
alt_interface_name))
1078
interface_names.add(alt_interface)
1079
# Is this a D-Bus signal?
1080
if getattr(attribute, "_dbus_is_signal", False):
1081
# Extract the original non-method undecorated
1082
# function by black magic
1083
nonmethod_func = (dict(
1084
zip(attribute.func_code.co_freevars,
1085
attribute.__closure__))["func"]
1087
# Create a new, but exactly alike, function
1088
# object, and decorate it to be a new D-Bus signal
1089
# with the alternate D-Bus interface name
1090
new_function = (dbus.service.signal
1092
attribute._dbus_signature)
1093
(types.FunctionType(
1094
nonmethod_func.func_code,
1095
nonmethod_func.func_globals,
1096
nonmethod_func.func_name,
1097
nonmethod_func.func_defaults,
1098
nonmethod_func.func_closure)))
1099
# Copy annotations, if any
1101
new_function._dbus_annotations = (
1102
dict(attribute._dbus_annotations))
1103
except AttributeError:
1105
# Define a creator of a function to call both the
1106
# original and alternate functions, so both the
1107
# original and alternate signals gets sent when
1108
# the function is called
1109
def fixscope(func1, func2):
1110
"""This function is a scope container to pass
1111
func1 and func2 to the "call_both" function
1112
outside of its arguments"""
1113
def call_both(*args, **kwargs):
1114
"""This function will emit two D-Bus
1115
signals by calling func1 and func2"""
1116
func1(*args, **kwargs)
1117
func2(*args, **kwargs)
1119
# Create the "call_both" function and add it to
1121
attr[attrname] = fixscope(attribute, new_function)
1122
# Is this a D-Bus method?
1123
elif getattr(attribute, "_dbus_is_method", False):
1124
# Create a new, but exactly alike, function
1125
# object. Decorate it to be a new D-Bus method
1126
# with the alternate D-Bus interface name. Add it
1128
attr[attrname] = (dbus.service.method
1130
attribute._dbus_in_signature,
1131
attribute._dbus_out_signature)
1133
(attribute.func_code,
1134
attribute.func_globals,
1135
attribute.func_name,
1136
attribute.func_defaults,
1137
attribute.func_closure)))
1138
# Copy annotations, if any
1140
attr[attrname]._dbus_annotations = (
1141
dict(attribute._dbus_annotations))
1142
except AttributeError:
1144
# Is this a D-Bus property?
1145
elif getattr(attribute, "_dbus_is_property", False):
1146
# Create a new, but exactly alike, function
1147
# object, and decorate it to be a new D-Bus
1148
# property with the alternate D-Bus interface
1149
# name. Add it to the class.
1150
attr[attrname] = (dbus_service_property
1152
attribute._dbus_signature,
1153
attribute._dbus_access,
1155
._dbus_get_args_options
1158
(attribute.func_code,
1159
attribute.func_globals,
1160
attribute.func_name,
1161
attribute.func_defaults,
1162
attribute.func_closure)))
1163
# Copy annotations, if any
1165
attr[attrname]._dbus_annotations = (
1166
dict(attribute._dbus_annotations))
1167
except AttributeError:
1169
# Is this a D-Bus interface?
1170
elif getattr(attribute, "_dbus_is_interface", False):
1171
# Create a new, but exactly alike, function
1172
# object. Decorate it to be a new D-Bus interface
1173
# with the alternate D-Bus interface name. Add it
1175
attr[attrname] = (dbus_interface_annotations
1178
(attribute.func_code,
1179
attribute.func_globals,
1180
attribute.func_name,
1181
attribute.func_defaults,
1182
attribute.func_closure)))
1184
# Deprecate all alternate interfaces
1185
iname="_AlternateDBusNames_interface_annotation{}"
1186
for interface_name in interface_names:
1187
@dbus_interface_annotations(interface_name)
1189
return { "org.freedesktop.DBus.Deprecated":
1191
# Find an unused name
1192
for aname in (iname.format(i)
1193
for i in itertools.count()):
1194
if aname not in attr:
1198
# Replace the class with a new subclass of it with
1199
# methods, signals, etc. as created above.
1200
cls = type(b"{}Alternate".format(cls.__name__),
1206
@alternate_dbus_interfaces({"se.recompile.Mandos":
1207
"se.bsnet.fukt.Mandos"})
1208
707
class ClientDBus(Client, DBusObjectWithProperties):
1209
708
"""A Client class using D-Bus
1216
715
runtime_expansions = (Client.runtime_expansions
1217
+ ("dbus_object_path",))
716
+ (u"dbus_object_path",))
1219
718
# dbus.service.Object doesn't use super(), so we can't either.
1221
720
def __init__(self, bus = None, *args, **kwargs):
721
self._approvals_pending = 0
1223
723
Client.__init__(self, *args, **kwargs)
1224
724
# Only now, when this client is initialized, can it show up on
1226
726
client_object_name = unicode(self.name).translate(
1227
{ord("."): ord("_"),
1228
ord("-"): ord("_")})
727
{ord(u"."): ord(u"_"),
728
ord(u"-"): ord(u"_")})
1229
729
self.dbus_object_path = (dbus.ObjectPath
1230
("/clients/" + client_object_name))
730
(u"/clients/" + client_object_name))
1231
731
DBusObjectWithProperties.__init__(self, self.bus,
1232
732
self.dbus_object_path)
1234
def notifychangeproperty(transform_func,
1235
dbus_name, type_func=lambda x: x,
1237
""" Modify a variable so that it's a property which announces
1238
its changes to DBus.
1240
transform_fun: Function that takes a value and a variant_level
1241
and transforms it to a D-Bus type.
1242
dbus_name: D-Bus name of the variable
1243
type_func: Function that transform the value before sending it
1244
to the D-Bus. Default: no transform
1245
variant_level: D-Bus variant level. Default: 1
1247
attrname = "_{}".format(dbus_name)
1248
def setter(self, value):
1249
if hasattr(self, "dbus_object_path"):
1250
if (not hasattr(self, attrname) or
1251
type_func(getattr(self, attrname, None))
1252
!= type_func(value)):
1253
dbus_value = transform_func(type_func(value),
1256
self.PropertyChanged(dbus.String(dbus_name),
1258
setattr(self, attrname, value)
1260
return property(lambda self: getattr(self, attrname), setter)
1262
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1263
approvals_pending = notifychangeproperty(dbus.Boolean,
1266
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1267
last_enabled = notifychangeproperty(datetime_to_dbus,
1269
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1270
type_func = lambda checker:
1271
checker is not None)
1272
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1274
last_checker_status = notifychangeproperty(dbus.Int16,
1275
"LastCheckerStatus")
1276
last_approval_request = notifychangeproperty(
1277
datetime_to_dbus, "LastApprovalRequest")
1278
approved_by_default = notifychangeproperty(dbus.Boolean,
1279
"ApprovedByDefault")
1280
approval_delay = notifychangeproperty(dbus.UInt64,
1283
lambda td: td.total_seconds()
1285
approval_duration = notifychangeproperty(
1286
dbus.UInt64, "ApprovalDuration",
1287
type_func = lambda td: td.total_seconds() * 1000)
1288
host = notifychangeproperty(dbus.String, "Host")
1289
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1290
type_func = lambda td:
1291
td.total_seconds() * 1000)
1292
extended_timeout = notifychangeproperty(
1293
dbus.UInt64, "ExtendedTimeout",
1294
type_func = lambda td: td.total_seconds() * 1000)
1295
interval = notifychangeproperty(dbus.UInt64,
1298
lambda td: td.total_seconds()
1300
checker_command = notifychangeproperty(dbus.String, "Checker")
1302
del notifychangeproperty
734
def _get_approvals_pending(self):
735
return self._approvals_pending
736
def _set_approvals_pending(self, value):
737
old_value = self._approvals_pending
738
self._approvals_pending = value
740
if (hasattr(self, "dbus_object_path")
741
and bval is not bool(old_value)):
742
dbus_bool = dbus.Boolean(bval, variant_level=1)
743
self.PropertyChanged(dbus.String(u"ApprovalPending"),
746
approvals_pending = property(_get_approvals_pending,
747
_set_approvals_pending)
748
del _get_approvals_pending, _set_approvals_pending
751
def _datetime_to_dbus(dt, variant_level=0):
752
"""Convert a UTC datetime.datetime() to a D-Bus type."""
753
return dbus.String(dt.isoformat(),
754
variant_level=variant_level)
757
oldstate = getattr(self, u"enabled", False)
758
r = Client.enable(self)
759
if oldstate != self.enabled:
761
self.PropertyChanged(dbus.String(u"Enabled"),
762
dbus.Boolean(True, variant_level=1))
763
self.PropertyChanged(
764
dbus.String(u"LastEnabled"),
765
self._datetime_to_dbus(self.last_enabled,
769
def disable(self, quiet = False):
770
oldstate = getattr(self, u"enabled", False)
771
r = Client.disable(self, quiet=quiet)
772
if not quiet and oldstate != self.enabled:
774
self.PropertyChanged(dbus.String(u"Enabled"),
775
dbus.Boolean(False, variant_level=1))
1304
778
def __del__(self, *args, **kwargs):
1306
780
self.remove_from_connection()
1307
781
except LookupError:
1309
if hasattr(DBusObjectWithProperties, "__del__"):
783
if hasattr(DBusObjectWithProperties, u"__del__"):
1310
784
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1311
785
Client.__del__(self, *args, **kwargs)
1329
806
return Client.checker_callback(self, pid, condition, command,
1330
807
*args, **kwargs)
809
def checked_ok(self, *args, **kwargs):
810
r = Client.checked_ok(self, *args, **kwargs)
812
self.PropertyChanged(
813
dbus.String(u"LastCheckedOK"),
814
(self._datetime_to_dbus(self.last_checked_ok,
1332
818
def start_checker(self, *args, **kwargs):
1333
old_checker_pid = getattr(self.checker, "pid", None)
819
old_checker = self.checker
820
if self.checker is not None:
821
old_checker_pid = self.checker.pid
823
old_checker_pid = None
1334
824
r = Client.start_checker(self, *args, **kwargs)
1335
825
# Only if new checker process was started
1336
826
if (self.checker is not None
1337
827
and old_checker_pid != self.checker.pid):
1338
828
# Emit D-Bus signal
1339
829
self.CheckerStarted(self.current_checker_command)
830
self.PropertyChanged(
831
dbus.String(u"CheckerRunning"),
832
dbus.Boolean(True, variant_level=1))
835
def stop_checker(self, *args, **kwargs):
836
old_checker = getattr(self, u"checker", None)
837
r = Client.stop_checker(self, *args, **kwargs)
838
if (old_checker is not None
839
and getattr(self, u"checker", None) is None):
840
self.PropertyChanged(dbus.String(u"CheckerRunning"),
841
dbus.Boolean(False, variant_level=1))
1342
844
def _reset_approved(self):
1343
self.approved = None
845
self._approved = None
1346
848
def approve(self, value=True):
1347
self.approved = value
1348
gobject.timeout_add(int(self.approval_duration.total_seconds()
1349
* 1000), self._reset_approved)
1350
849
self.send_changedstate()
850
self._approved = value
851
gobject.timeout_add(self._timedelta_to_milliseconds
852
(self.approval_duration),
853
self._reset_approved)
1352
856
## D-Bus methods, signals & properties
1353
_interface = "se.recompile.Mandos.Client"
1357
@dbus_interface_annotations(_interface)
1359
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
857
_interface = u"se.bsnet.fukt.Mandos.Client"
1364
861
# CheckerCompleted - signal
1365
@dbus.service.signal(_interface, signature="nxs")
862
@dbus.service.signal(_interface, signature=u"nxs")
1366
863
def CheckerCompleted(self, exitcode, waitstatus, command):
1370
867
# CheckerStarted - signal
1371
@dbus.service.signal(_interface, signature="s")
868
@dbus.service.signal(_interface, signature=u"s")
1372
869
def CheckerStarted(self, command):
1376
873
# PropertyChanged - signal
1377
@dbus.service.signal(_interface, signature="sv")
874
@dbus.service.signal(_interface, signature=u"sv")
1378
875
def PropertyChanged(self, property, value):
1440
937
# ApprovalPending - property
1441
@dbus_service_property(_interface, signature="b", access="read")
938
@dbus_service_property(_interface, signature=u"b", access=u"read")
1442
939
def ApprovalPending_dbus_property(self):
1443
940
return dbus.Boolean(bool(self.approvals_pending))
1445
942
# ApprovedByDefault - property
1446
@dbus_service_property(_interface, signature="b",
943
@dbus_service_property(_interface, signature=u"b",
1448
945
def ApprovedByDefault_dbus_property(self, value=None):
1449
946
if value is None: # get
1450
947
return dbus.Boolean(self.approved_by_default)
1451
948
self.approved_by_default = bool(value)
950
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
951
dbus.Boolean(value, variant_level=1))
1453
953
# ApprovalDelay - property
1454
@dbus_service_property(_interface, signature="t",
954
@dbus_service_property(_interface, signature=u"t",
1456
956
def ApprovalDelay_dbus_property(self, value=None):
1457
957
if value is None: # get
1458
return dbus.UInt64(self.approval_delay.total_seconds()
958
return dbus.UInt64(self.approval_delay_milliseconds())
1460
959
self.approval_delay = datetime.timedelta(0, 0, 0, value)
961
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
962
dbus.UInt64(value, variant_level=1))
1462
964
# ApprovalDuration - property
1463
@dbus_service_property(_interface, signature="t",
965
@dbus_service_property(_interface, signature=u"t",
1465
967
def ApprovalDuration_dbus_property(self, value=None):
1466
968
if value is None: # get
1467
return dbus.UInt64(self.approval_duration.total_seconds()
969
return dbus.UInt64(self._timedelta_to_milliseconds(
970
self.approval_duration))
1469
971
self.approval_duration = datetime.timedelta(0, 0, 0, value)
973
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
974
dbus.UInt64(value, variant_level=1))
1471
976
# Name - property
1472
@dbus_service_property(_interface, signature="s", access="read")
977
@dbus_service_property(_interface, signature=u"s", access=u"read")
1473
978
def Name_dbus_property(self):
1474
979
return dbus.String(self.name)
1476
981
# Fingerprint - property
1477
@dbus_service_property(_interface, signature="s", access="read")
982
@dbus_service_property(_interface, signature=u"s", access=u"read")
1478
983
def Fingerprint_dbus_property(self):
1479
984
return dbus.String(self.fingerprint)
1481
986
# Host - property
1482
@dbus_service_property(_interface, signature="s",
987
@dbus_service_property(_interface, signature=u"s",
1484
989
def Host_dbus_property(self, value=None):
1485
990
if value is None: # get
1486
991
return dbus.String(self.host)
1487
self.host = unicode(value)
994
self.PropertyChanged(dbus.String(u"Host"),
995
dbus.String(value, variant_level=1))
1489
997
# Created - property
1490
@dbus_service_property(_interface, signature="s", access="read")
998
@dbus_service_property(_interface, signature=u"s", access=u"read")
1491
999
def Created_dbus_property(self):
1492
return datetime_to_dbus(self.created)
1000
return dbus.String(self._datetime_to_dbus(self.created))
1494
1002
# LastEnabled - property
1495
@dbus_service_property(_interface, signature="s", access="read")
1003
@dbus_service_property(_interface, signature=u"s", access=u"read")
1496
1004
def LastEnabled_dbus_property(self):
1497
return datetime_to_dbus(self.last_enabled)
1005
if self.last_enabled is None:
1006
return dbus.String(u"")
1007
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1499
1009
# Enabled - property
1500
@dbus_service_property(_interface, signature="b",
1010
@dbus_service_property(_interface, signature=u"b",
1011
access=u"readwrite")
1502
1012
def Enabled_dbus_property(self, value=None):
1503
1013
if value is None: # get
1504
1014
return dbus.Boolean(self.enabled)
1510
1020
# LastCheckedOK - property
1511
@dbus_service_property(_interface, signature="s",
1021
@dbus_service_property(_interface, signature=u"s",
1022
access=u"readwrite")
1513
1023
def LastCheckedOK_dbus_property(self, value=None):
1514
1024
if value is not None:
1515
1025
self.checked_ok()
1517
return datetime_to_dbus(self.last_checked_ok)
1519
# LastCheckerStatus - property
1520
@dbus_service_property(_interface, signature="n",
1522
def LastCheckerStatus_dbus_property(self):
1523
return dbus.Int16(self.last_checker_status)
1525
# Expires - property
1526
@dbus_service_property(_interface, signature="s", access="read")
1527
def Expires_dbus_property(self):
1528
return datetime_to_dbus(self.expires)
1530
# LastApprovalRequest - property
1531
@dbus_service_property(_interface, signature="s", access="read")
1532
def LastApprovalRequest_dbus_property(self):
1533
return datetime_to_dbus(self.last_approval_request)
1027
if self.last_checked_ok is None:
1028
return dbus.String(u"")
1029
return dbus.String(self._datetime_to_dbus(self
1535
1032
# Timeout - property
1536
@dbus_service_property(_interface, signature="t",
1033
@dbus_service_property(_interface, signature=u"t",
1034
access=u"readwrite")
1538
1035
def Timeout_dbus_property(self, value=None):
1539
1036
if value is None: # get
1540
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1541
old_timeout = self.timeout
1037
return dbus.UInt64(self.timeout_milliseconds())
1542
1038
self.timeout = datetime.timedelta(0, 0, 0, value)
1543
# Reschedule disabling
1545
now = datetime.datetime.utcnow()
1546
self.expires += self.timeout - old_timeout
1547
if self.expires <= now:
1548
# The timeout has passed
1551
if (getattr(self, "disable_initiator_tag", None)
1554
gobject.source_remove(self.disable_initiator_tag)
1555
self.disable_initiator_tag = (
1556
gobject.timeout_add(
1557
int((self.expires - now).total_seconds()
1558
* 1000), self.disable))
1560
# ExtendedTimeout - property
1561
@dbus_service_property(_interface, signature="t",
1563
def ExtendedTimeout_dbus_property(self, value=None):
1564
if value is None: # get
1565
return dbus.UInt64(self.extended_timeout.total_seconds()
1567
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1040
self.PropertyChanged(dbus.String(u"Timeout"),
1041
dbus.UInt64(value, variant_level=1))
1042
if getattr(self, u"disable_initiator_tag", None) is None:
1044
# Reschedule timeout
1045
gobject.source_remove(self.disable_initiator_tag)
1046
self.disable_initiator_tag = None
1047
time_to_die = (self.
1048
_timedelta_to_milliseconds((self
1053
if time_to_die <= 0:
1054
# The timeout has passed
1057
self.disable_initiator_tag = (gobject.timeout_add
1058
(time_to_die, self.disable))
1569
1060
# Interval - property
1570
@dbus_service_property(_interface, signature="t",
1061
@dbus_service_property(_interface, signature=u"t",
1062
access=u"readwrite")
1572
1063
def Interval_dbus_property(self, value=None):
1573
1064
if value is None: # get
1574
return dbus.UInt64(self.interval.total_seconds() * 1000)
1065
return dbus.UInt64(self.interval_milliseconds())
1575
1066
self.interval = datetime.timedelta(0, 0, 0, value)
1576
if getattr(self, "checker_initiator_tag", None) is None:
1068
self.PropertyChanged(dbus.String(u"Interval"),
1069
dbus.UInt64(value, variant_level=1))
1070
if getattr(self, u"checker_initiator_tag", None) is None:
1579
# Reschedule checker run
1580
gobject.source_remove(self.checker_initiator_tag)
1581
self.checker_initiator_tag = (gobject.timeout_add
1582
(value, self.start_checker))
1583
self.start_checker() # Start one now, too
1072
# Reschedule checker run
1073
gobject.source_remove(self.checker_initiator_tag)
1074
self.checker_initiator_tag = (gobject.timeout_add
1075
(value, self.start_checker))
1076
self.start_checker() # Start one now, too
1585
1078
# Checker - property
1586
@dbus_service_property(_interface, signature="s",
1079
@dbus_service_property(_interface, signature=u"s",
1080
access=u"readwrite")
1588
1081
def Checker_dbus_property(self, value=None):
1589
1082
if value is None: # get
1590
1083
return dbus.String(self.checker_command)
1591
self.checker_command = unicode(value)
1084
self.checker_command = value
1086
self.PropertyChanged(dbus.String(u"Checker"),
1087
dbus.String(self.checker_command,
1593
1090
# CheckerRunning - property
1594
@dbus_service_property(_interface, signature="b",
1091
@dbus_service_property(_interface, signature=u"b",
1092
access=u"readwrite")
1596
1093
def CheckerRunning_dbus_property(self, value=None):
1597
1094
if value is None: # get
1598
1095
return dbus.Boolean(self.checker is not None)
1650
1147
def handle(self):
1651
1148
with contextlib.closing(self.server.child_pipe) as child_pipe:
1652
logger.info("TCP connection from: %s",
1149
logger.info(u"TCP connection from: %s",
1653
1150
unicode(self.client_address))
1654
logger.debug("Pipe FD: %d",
1151
logger.debug(u"Pipe FD: %d",
1655
1152
self.server.child_pipe.fileno())
1657
1154
session = (gnutls.connection
1658
1155
.ClientSession(self.request,
1659
1156
gnutls.connection
1660
1157
.X509Credentials()))
1662
1159
# Note: gnutls.connection.X509Credentials is really a
1663
1160
# generic GnuTLS certificate credentials object so long as
1664
1161
# no X.509 keys are added to it. Therefore, we can use it
1665
1162
# here despite using OpenPGP certificates.
1667
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1668
# "+AES-256-CBC", "+SHA1",
1669
# "+COMP-NULL", "+CTYPE-OPENPGP",
1164
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1165
# u"+AES-256-CBC", u"+SHA1",
1166
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1671
1168
# Use a fallback default, since this MUST be set.
1672
1169
priority = self.server.gnutls_priority
1673
1170
if priority is None:
1171
priority = u"NORMAL"
1675
1172
(gnutls.library.functions
1676
1173
.gnutls_priority_set_direct(session._c_object,
1677
1174
priority, None))
1679
1176
# Start communication using the Mandos protocol
1680
1177
# Get protocol number
1681
1178
line = self.request.makefile().readline()
1682
logger.debug("Protocol version: %r", line)
1179
logger.debug(u"Protocol version: %r", line)
1684
1181
if int(line.strip().split()[0]) > 1:
1685
raise RuntimeError(line)
1686
except (ValueError, IndexError, RuntimeError) as error:
1687
logger.error("Unknown protocol version: %s", error)
1183
except (ValueError, IndexError, RuntimeError), error:
1184
logger.error(u"Unknown protocol version: %s", error)
1690
1187
# Start GnuTLS connection
1692
1189
session.handshake()
1693
except gnutls.errors.GNUTLSError as error:
1694
logger.warning("Handshake failed: %s", error)
1190
except gnutls.errors.GNUTLSError, error:
1191
logger.warning(u"Handshake failed: %s", error)
1695
1192
# Do not run session.bye() here: the session is not
1696
1193
# established. Just abandon the request.
1698
logger.debug("Handshake succeeded")
1195
logger.debug(u"Handshake succeeded")
1700
1197
approval_required = False
1703
1200
fpr = self.fingerprint(self.peer_certificate
1706
gnutls.errors.GNUTLSError) as error:
1707
logger.warning("Bad certificate: %s", error)
1202
except (TypeError, gnutls.errors.GNUTLSError), error:
1203
logger.warning(u"Bad certificate: %s", error)
1709
logger.debug("Fingerprint: %s", fpr)
1205
logger.debug(u"Fingerprint: %s", fpr)
1712
1208
client = ProxyClient(child_pipe, fpr,
1713
1209
self.client_address)
1906
1397
use_ipv6: Boolean; to use IPv6 or not
1908
1399
def __init__(self, server_address, RequestHandlerClass,
1909
interface=None, use_ipv6=True, socketfd=None):
1910
"""If socketfd is set, use that file descriptor instead of
1911
creating a new one with socket.socket().
1400
interface=None, use_ipv6=True):
1913
1401
self.interface = interface
1915
1403
self.address_family = socket.AF_INET6
1916
if socketfd is not None:
1917
# Save the file descriptor
1918
self.socketfd = socketfd
1919
# Save the original socket.socket() function
1920
self.socket_socket = socket.socket
1921
# To implement --socket, we monkey patch socket.socket.
1923
# (When socketserver.TCPServer is a new-style class, we
1924
# could make self.socket into a property instead of monkey
1925
# patching socket.socket.)
1927
# Create a one-time-only replacement for socket.socket()
1928
@functools.wraps(socket.socket)
1929
def socket_wrapper(*args, **kwargs):
1930
# Restore original function so subsequent calls are
1932
socket.socket = self.socket_socket
1933
del self.socket_socket
1934
# This time only, return a new socket object from the
1935
# saved file descriptor.
1936
return socket.fromfd(self.socketfd, *args, **kwargs)
1937
# Replace socket.socket() function with wrapper
1938
socket.socket = socket_wrapper
1939
# The socketserver.TCPServer.__init__ will call
1940
# socket.socket(), which might be our replacement,
1941
# socket_wrapper(), if socketfd was set.
1942
1404
socketserver.TCPServer.__init__(self, server_address,
1943
1405
RequestHandlerClass)
1945
1406
def server_bind(self):
1946
1407
"""This overrides the normal server_bind() function
1947
1408
to bind to an interface if one was specified, and also NOT to
1948
1409
bind to an address or port if they were not specified."""
1949
1410
if self.interface is not None:
1950
1411
if SO_BINDTODEVICE is None:
1951
logger.error("SO_BINDTODEVICE does not exist;"
1952
" cannot bind to interface %s",
1412
logger.error(u"SO_BINDTODEVICE does not exist;"
1413
u" cannot bind to interface %s",
1953
1414
self.interface)
1956
1417
self.socket.setsockopt(socket.SOL_SOCKET,
1957
1418
SO_BINDTODEVICE,
1958
(self.interface + "\0")
1960
except socket.error as error:
1961
if error.errno == errno.EPERM:
1962
logger.error("No permission to bind to"
1963
" interface %s", self.interface)
1964
elif error.errno == errno.ENOPROTOOPT:
1965
logger.error("SO_BINDTODEVICE not available;"
1966
" cannot bind to interface %s",
1968
elif error.errno == errno.ENODEV:
1969
logger.error("Interface %s does not exist,"
1970
" cannot bind", self.interface)
1421
except socket.error, error:
1422
if error[0] == errno.EPERM:
1423
logger.error(u"No permission to"
1424
u" bind to interface %s",
1426
elif error[0] == errno.ENOPROTOOPT:
1427
logger.error(u"SO_BINDTODEVICE not available;"
1428
u" cannot bind to interface %s",
1973
1432
# Only bind(2) the socket if we really need to.
1974
1433
if self.server_address[0] or self.server_address[1]:
1975
1434
if not self.server_address[0]:
1976
1435
if self.address_family == socket.AF_INET6:
1977
any_address = "::" # in6addr_any
1436
any_address = u"::" # in6addr_any
1979
any_address = "0.0.0.0" # INADDR_ANY
1438
any_address = socket.INADDR_ANY
1980
1439
self.server_address = (any_address,
1981
1440
self.server_address[1])
1982
1441
elif not self.server_address[1]:
2047
1513
fpr = request[1]
2048
1514
address = request[2]
2050
for c in self.clients.itervalues():
1516
for c in self.clients:
2051
1517
if c.fingerprint == fpr:
2055
logger.info("Client not found for fingerprint: %s, ad"
2056
"dress: %s", fpr, address)
1521
logger.warning(u"Client not found for fingerprint: %s, ad"
1522
u"dress: %s", fpr, address)
2057
1523
if self.use_dbus:
2058
1524
# Emit D-Bus signal
2059
mandos_dbus_service.ClientNotFound(fpr,
1525
mandos_dbus_service.ClientNotFound(fpr, address[0])
2061
1526
parent_pipe.send(False)
2064
1529
gobject.io_add_watch(parent_pipe.fileno(),
2065
1530
gobject.IO_IN | gobject.IO_HUP,
2066
1531
functools.partial(self.handle_ipc,
1532
parent_pipe = parent_pipe,
1533
client_object = client))
2072
1534
parent_pipe.send(True)
2073
# remove the old hook in favor of the new above hook on
1535
# remove the old hook in favor of the new above hook on same fileno
2076
1537
if command == 'funcall':
2077
1538
funcname = request[1]
2078
1539
args = request[2]
2079
1540
kwargs = request[3]
2081
parent_pipe.send(('data', getattr(client_object,
1542
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
2085
1544
if command == 'getattr':
2086
1545
attrname = request[1]
2087
1546
if callable(client_object.__getattribute__(attrname)):
2088
1547
parent_pipe.send(('function',))
2090
parent_pipe.send(('data', client_object
2091
.__getattribute__(attrname)))
1549
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
2093
1551
if command == 'setattr':
2094
1552
attrname = request[1]
2095
1553
value = request[2]
2096
1554
setattr(client_object, attrname, value)
2101
def rfc3339_duration_to_delta(duration):
2102
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2104
>>> rfc3339_duration_to_delta("P7D")
2105
datetime.timedelta(7)
2106
>>> rfc3339_duration_to_delta("PT60S")
2107
datetime.timedelta(0, 60)
2108
>>> rfc3339_duration_to_delta("PT60M")
2109
datetime.timedelta(0, 3600)
2110
>>> rfc3339_duration_to_delta("PT24H")
2111
datetime.timedelta(1)
2112
>>> rfc3339_duration_to_delta("P1W")
2113
datetime.timedelta(7)
2114
>>> rfc3339_duration_to_delta("PT5M30S")
2115
datetime.timedelta(0, 330)
2116
>>> rfc3339_duration_to_delta("P1DT3M20S")
2117
datetime.timedelta(1, 200)
2120
# Parsing an RFC 3339 duration with regular expressions is not
2121
# possible - there would have to be multiple places for the same
2122
# values, like seconds. The current code, while more esoteric, is
2123
# cleaner without depending on a parsing library. If Python had a
2124
# built-in library for parsing we would use it, but we'd like to
2125
# avoid excessive use of external libraries.
2127
# New type for defining tokens, syntax, and semantics all-in-one
2128
Token = collections.namedtuple("Token",
2129
("regexp", # To match token; if
2130
# "value" is not None,
2131
# must have a "group"
2133
"value", # datetime.timedelta or
2135
"followers")) # Tokens valid after
2137
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2138
# the "duration" ABNF definition in RFC 3339, Appendix A.
2139
token_end = Token(re.compile(r"$"), None, frozenset())
2140
token_second = Token(re.compile(r"(\d+)S"),
2141
datetime.timedelta(seconds=1),
2142
frozenset((token_end,)))
2143
token_minute = Token(re.compile(r"(\d+)M"),
2144
datetime.timedelta(minutes=1),
2145
frozenset((token_second, token_end)))
2146
token_hour = Token(re.compile(r"(\d+)H"),
2147
datetime.timedelta(hours=1),
2148
frozenset((token_minute, token_end)))
2149
token_time = Token(re.compile(r"T"),
2151
frozenset((token_hour, token_minute,
2153
token_day = Token(re.compile(r"(\d+)D"),
2154
datetime.timedelta(days=1),
2155
frozenset((token_time, token_end)))
2156
token_month = Token(re.compile(r"(\d+)M"),
2157
datetime.timedelta(weeks=4),
2158
frozenset((token_day, token_end)))
2159
token_year = Token(re.compile(r"(\d+)Y"),
2160
datetime.timedelta(weeks=52),
2161
frozenset((token_month, token_end)))
2162
token_week = Token(re.compile(r"(\d+)W"),
2163
datetime.timedelta(weeks=1),
2164
frozenset((token_end,)))
2165
token_duration = Token(re.compile(r"P"), None,
2166
frozenset((token_year, token_month,
2167
token_day, token_time,
2169
# Define starting values
2170
value = datetime.timedelta() # Value so far
2172
followers = frozenset((token_duration,)) # Following valid tokens
2173
s = duration # String left to parse
2174
# Loop until end token is found
2175
while found_token is not token_end:
2176
# Search for any currently valid tokens
2177
for token in followers:
2178
match = token.regexp.match(s)
2179
if match is not None:
2181
if token.value is not None:
2182
# Value found, parse digits
2183
factor = int(match.group(1), 10)
2184
# Add to value so far
2185
value += factor * token.value
2186
# Strip token from string
2187
s = token.regexp.sub("", s, 1)
2190
# Set valid next tokens
2191
followers = found_token.followers
2194
# No currently valid tokens were found
2195
raise ValueError("Invalid RFC 3339 duration")
2200
1559
def string_to_delta(interval):
2201
1560
"""Parse a string and return a datetime.timedelta
2203
>>> string_to_delta('7d')
1562
>>> string_to_delta(u'7d')
2204
1563
datetime.timedelta(7)
2205
>>> string_to_delta('60s')
1564
>>> string_to_delta(u'60s')
2206
1565
datetime.timedelta(0, 60)
2207
>>> string_to_delta('60m')
1566
>>> string_to_delta(u'60m')
2208
1567
datetime.timedelta(0, 3600)
2209
>>> string_to_delta('24h')
1568
>>> string_to_delta(u'24h')
2210
1569
datetime.timedelta(1)
2211
>>> string_to_delta('1w')
1570
>>> string_to_delta(u'1w')
2212
1571
datetime.timedelta(7)
2213
>>> string_to_delta('5m 30s')
1572
>>> string_to_delta(u'5m 30s')
2214
1573
datetime.timedelta(0, 330)
2218
return rfc3339_duration_to_delta(interval)
2222
1575
timevalue = datetime.timedelta(0)
2223
1576
for s in interval.split():
1578
suffix = unicode(s[-1])
2226
1579
value = int(s[:-1])
2228
1581
delta = datetime.timedelta(value)
1582
elif suffix == u"s":
2230
1583
delta = datetime.timedelta(0, value)
1584
elif suffix == u"m":
2232
1585
delta = datetime.timedelta(0, 0, 0, 0, value)
1586
elif suffix == u"h":
2234
1587
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1588
elif suffix == u"w":
2236
1589
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2238
raise ValueError("Unknown suffix {!r}"
2240
except IndexError as e:
2241
raise ValueError(*(e.args))
1591
raise ValueError(u"Unknown suffix %r" % suffix)
1592
except (ValueError, IndexError), e:
1593
raise ValueError(e.message)
2242
1594
timevalue += delta
2243
1595
return timevalue
1598
def if_nametoindex(interface):
1599
"""Call the C function if_nametoindex(), or equivalent
1601
Note: This function cannot accept a unicode string."""
1602
global if_nametoindex
1604
if_nametoindex = (ctypes.cdll.LoadLibrary
1605
(ctypes.util.find_library(u"c"))
1607
except (OSError, AttributeError):
1608
logger.warning(u"Doing if_nametoindex the hard way")
1609
def if_nametoindex(interface):
1610
"Get an interface index the hard way, i.e. using fcntl()"
1611
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1612
with contextlib.closing(socket.socket()) as s:
1613
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1614
struct.pack(str(u"16s16x"),
1616
interface_index = struct.unpack(str(u"I"),
1618
return interface_index
1619
return if_nametoindex(interface)
2246
1622
def daemon(nochdir = False, noclose = False):
2247
1623
"""See daemon(3). Standard BSD Unix function.
2272
1649
##################################################################
2273
1650
# Parsing of options, both command line and config file
2275
parser = argparse.ArgumentParser()
2276
parser.add_argument("-v", "--version", action="version",
2277
version = "%(prog)s {}".format(version),
2278
help="show version number and exit")
2279
parser.add_argument("-i", "--interface", metavar="IF",
2280
help="Bind to interface IF")
2281
parser.add_argument("-a", "--address",
2282
help="Address to listen for requests on")
2283
parser.add_argument("-p", "--port", type=int,
2284
help="Port number to receive requests on")
2285
parser.add_argument("--check", action="store_true",
2286
help="Run self-test")
2287
parser.add_argument("--debug", action="store_true",
2288
help="Debug mode; run in foreground and log"
2289
" to terminal", default=None)
2290
parser.add_argument("--debuglevel", metavar="LEVEL",
2291
help="Debug level for stdout output")
2292
parser.add_argument("--priority", help="GnuTLS"
2293
" priority string (see GnuTLS documentation)")
2294
parser.add_argument("--servicename",
2295
metavar="NAME", help="Zeroconf service name")
2296
parser.add_argument("--configdir",
2297
default="/etc/mandos", metavar="DIR",
2298
help="Directory to search for configuration"
2300
parser.add_argument("--no-dbus", action="store_false",
2301
dest="use_dbus", help="Do not provide D-Bus"
2302
" system bus interface", default=None)
2303
parser.add_argument("--no-ipv6", action="store_false",
2304
dest="use_ipv6", help="Do not use IPv6",
2306
parser.add_argument("--no-restore", action="store_false",
2307
dest="restore", help="Do not restore stored"
2308
" state", default=None)
2309
parser.add_argument("--socket", type=int,
2310
help="Specify a file descriptor to a network"
2311
" socket to use instead of creating one")
2312
parser.add_argument("--statedir", metavar="DIR",
2313
help="Directory to save/restore state in")
2314
parser.add_argument("--foreground", action="store_true",
2315
help="Run in foreground", default=None)
2316
parser.add_argument("--no-zeroconf", action="store_false",
2317
dest="zeroconf", help="Do not use Zeroconf",
2320
options = parser.parse_args()
1652
parser = optparse.OptionParser(version = "%%prog %s" % version)
1653
parser.add_option("-i", u"--interface", type=u"string",
1654
metavar="IF", help=u"Bind to interface IF")
1655
parser.add_option("-a", u"--address", type=u"string",
1656
help=u"Address to listen for requests on")
1657
parser.add_option("-p", u"--port", type=u"int",
1658
help=u"Port number to receive requests on")
1659
parser.add_option("--check", action=u"store_true",
1660
help=u"Run self-test")
1661
parser.add_option("--debug", action=u"store_true",
1662
help=u"Debug mode; run in foreground and log to"
1664
parser.add_option("--debuglevel", type=u"string", metavar="Level",
1665
help=u"Debug level for stdout output")
1666
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1667
u" priority string (see GnuTLS documentation)")
1668
parser.add_option("--servicename", type=u"string",
1669
metavar=u"NAME", help=u"Zeroconf service name")
1670
parser.add_option("--configdir", type=u"string",
1671
default=u"/etc/mandos", metavar=u"DIR",
1672
help=u"Directory to search for configuration"
1674
parser.add_option("--no-dbus", action=u"store_false",
1675
dest=u"use_dbus", help=u"Do not provide D-Bus"
1676
u" system bus interface")
1677
parser.add_option("--no-ipv6", action=u"store_false",
1678
dest=u"use_ipv6", help=u"Do not use IPv6")
1679
options = parser.parse_args()[0]
2322
1681
if options.check:
2324
fail_count, test_count = doctest.testmod()
2325
sys.exit(os.EX_OK if fail_count == 0 else 1)
2327
1686
# Default values for config file for server-global settings
2328
server_defaults = { "interface": "",
2333
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2334
"servicename": "Mandos",
2340
"statedir": "/var/lib/mandos",
2341
"foreground": "False",
1687
server_defaults = { u"interface": u"",
1692
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1693
u"servicename": u"Mandos",
1694
u"use_dbus": u"True",
1695
u"use_ipv6": u"True",
2345
1699
# Parse config file for server-global settings
2346
1700
server_config = configparser.SafeConfigParser(server_defaults)
2347
1701
del server_defaults
2348
1702
server_config.read(os.path.join(options.configdir,
2350
1704
# Convert the SafeConfigParser object to a dict
2351
1705
server_settings = server_config.defaults()
2352
1706
# Use the appropriate methods on the non-string config options
2353
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2354
server_settings[option] = server_config.getboolean("DEFAULT",
1707
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1708
server_settings[option] = server_config.getboolean(u"DEFAULT",
2356
1710
if server_settings["port"]:
2357
server_settings["port"] = server_config.getint("DEFAULT",
2359
if server_settings["socket"]:
2360
server_settings["socket"] = server_config.getint("DEFAULT",
2362
# Later, stdin will, and stdout and stderr might, be dup'ed
2363
# over with an opened os.devnull. But we don't want this to
2364
# happen with a supplied network socket.
2365
if 0 <= server_settings["socket"] <= 2:
2366
server_settings["socket"] = os.dup(server_settings
1711
server_settings["port"] = server_config.getint(u"DEFAULT",
2368
1713
del server_config
2370
1715
# Override the settings from the config file with command line
2371
1716
# options, if set.
2372
for option in ("interface", "address", "port", "debug",
2373
"priority", "servicename", "configdir",
2374
"use_dbus", "use_ipv6", "debuglevel", "restore",
2375
"statedir", "socket", "foreground", "zeroconf"):
1717
for option in (u"interface", u"address", u"port", u"debug",
1718
u"priority", u"servicename", u"configdir",
1719
u"use_dbus", u"use_ipv6", u"debuglevel"):
2376
1720
value = getattr(options, option)
2377
1721
if value is not None:
2378
1722
server_settings[option] = value
2380
1724
# Force all strings to be unicode
2381
1725
for option in server_settings.keys():
2382
if isinstance(server_settings[option], bytes):
2383
server_settings[option] = (server_settings[option]
2385
# Force all boolean options to be boolean
2386
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2387
"foreground", "zeroconf"):
2388
server_settings[option] = bool(server_settings[option])
2389
# Debug implies foreground
2390
if server_settings["debug"]:
2391
server_settings["foreground"] = True
1726
if type(server_settings[option]) is str:
1727
server_settings[option] = unicode(server_settings[option])
2392
1728
# Now we have our good server settings in "server_settings"
2394
1730
##################################################################
2396
if (not server_settings["zeroconf"] and
2397
not (server_settings["port"]
2398
or server_settings["socket"] != "")):
2399
parser.error("Needs port or socket to work without"
2402
1732
# For convenience
2403
debug = server_settings["debug"]
2404
debuglevel = server_settings["debuglevel"]
2405
use_dbus = server_settings["use_dbus"]
2406
use_ipv6 = server_settings["use_ipv6"]
2407
stored_state_path = os.path.join(server_settings["statedir"],
2409
foreground = server_settings["foreground"]
2410
zeroconf = server_settings["zeroconf"]
2413
initlogger(debug, logging.DEBUG)
2418
level = getattr(logging, debuglevel.upper())
2419
initlogger(debug, level)
2421
if server_settings["servicename"] != "Mandos":
1733
debug = server_settings[u"debug"]
1734
debuglevel = server_settings[u"debuglevel"]
1735
use_dbus = server_settings[u"use_dbus"]
1736
use_ipv6 = server_settings[u"use_ipv6"]
1738
if server_settings[u"servicename"] != u"Mandos":
2422
1739
syslogger.setFormatter(logging.Formatter
2423
('Mandos ({}) [%(process)d]:'
2424
' %(levelname)s: %(message)s'
2425
.format(server_settings
1740
(u'Mandos (%s) [%%(process)d]:'
1741
u' %%(levelname)s: %%(message)s'
1742
% server_settings[u"servicename"]))
2428
1744
# Parse config file with clients
2429
client_config = configparser.SafeConfigParser(Client
2431
client_config.read(os.path.join(server_settings["configdir"],
1745
client_defaults = { u"timeout": u"1h",
1747
u"checker": u"fping -q -- %%(host)s",
1749
u"approval_delay": u"0s",
1750
u"approval_duration": u"1s",
1752
client_config = configparser.SafeConfigParser(client_defaults)
1753
client_config.read(os.path.join(server_settings[u"configdir"],
2434
1756
global mandos_dbus_service
2435
1757
mandos_dbus_service = None
2438
if server_settings["socket"] != "":
2439
socketfd = server_settings["socket"]
2440
tcp_server = MandosServer((server_settings["address"],
2441
server_settings["port"]),
1759
tcp_server = MandosServer((server_settings[u"address"],
1760
server_settings[u"port"]),
2443
interface=(server_settings["interface"]
1762
interface=(server_settings[u"interface"]
2445
1764
use_ipv6=use_ipv6,
2446
1765
gnutls_priority=
2447
server_settings["priority"],
2451
pidfilename = "/run/mandos.pid"
2452
if not os.path.isdir("/run/."):
2453
pidfilename = "/var/run/mandos.pid"
1766
server_settings[u"priority"],
1769
pidfilename = u"/var/run/mandos.pid"
2456
pidfile = open(pidfilename, "w")
2457
except IOError as e:
2458
logger.error("Could not open file %r", pidfilename,
1771
pidfile = open(pidfilename, u"w")
1773
logger.error(u"Could not open file %r", pidfilename)
2461
for name in ("_mandos", "mandos", "nobody"):
1776
uid = pwd.getpwnam(u"_mandos").pw_uid
1777
gid = pwd.getpwnam(u"_mandos").pw_gid
2463
uid = pwd.getpwnam(name).pw_uid
2464
gid = pwd.getpwnam(name).pw_gid
1780
uid = pwd.getpwnam(u"mandos").pw_uid
1781
gid = pwd.getpwnam(u"mandos").pw_gid
2466
1782
except KeyError:
1784
uid = pwd.getpwnam(u"nobody").pw_uid
1785
gid = pwd.getpwnam(u"nobody").pw_gid
2474
except OSError as error:
2475
if error.errno != errno.EPERM:
1792
except OSError, error:
1793
if error[0] != errno.EPERM:
1796
if not debug and not debuglevel:
1797
syslogger.setLevel(logging.WARNING)
1798
console.setLevel(logging.WARNING)
1800
level = getattr(logging, debuglevel.upper())
1801
syslogger.setLevel(level)
1802
console.setLevel(level)
2479
1805
# Enable all possible GnuTLS debugging
2485
1811
@gnutls.library.types.gnutls_log_func
2486
1812
def debug_gnutls(level, string):
2487
logger.debug("GnuTLS: %s", string[:-1])
1813
logger.debug(u"GnuTLS: %s", string[:-1])
2489
1815
(gnutls.library.functions
2490
1816
.gnutls_global_set_log_function(debug_gnutls))
2492
1818
# Redirect stdin so all checkers get /dev/null
2493
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1819
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2494
1820
os.dup2(null, sys.stdin.fileno())
2498
# Need to fork before connecting to D-Bus
2500
# Close all input and output, do double fork, etc.
2503
# multiprocessing will use threads, so before we use gobject we
2504
# need to inform gobject that threads will be used.
2505
gobject.threads_init()
1824
# No console logging
1825
logger.removeHandler(console)
2507
1828
global main_loop
2508
1829
# From the Avahi example code
2509
DBusGMainLoop(set_as_default=True)
1830
DBusGMainLoop(set_as_default=True )
2510
1831
main_loop = gobject.MainLoop()
2511
1832
bus = dbus.SystemBus()
2512
1833
# End of Avahi example code
2515
bus_name = dbus.service.BusName("se.recompile.Mandos",
1836
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2516
1837
bus, do_not_queue=True)
2517
old_bus_name = (dbus.service.BusName
2518
("se.bsnet.fukt.Mandos", bus,
2520
except dbus.exceptions.NameExistsException as e:
2521
logger.error("Disabling D-Bus:", exc_info=e)
1838
except dbus.exceptions.NameExistsException, e:
1839
logger.error(unicode(e) + u", disabling D-Bus")
2522
1840
use_dbus = False
2523
server_settings["use_dbus"] = False
1841
server_settings[u"use_dbus"] = False
2524
1842
tcp_server.use_dbus = False
2526
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2527
service = AvahiServiceToSyslog(name =
2528
server_settings["servicename"],
2529
servicetype = "_mandos._tcp",
2530
protocol = protocol, bus = bus)
2531
if server_settings["interface"]:
2532
service.interface = (if_nametoindex
2533
(server_settings["interface"]
1843
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1844
service = AvahiService(name = server_settings[u"servicename"],
1845
servicetype = u"_mandos._tcp",
1846
protocol = protocol, bus = bus)
1847
if server_settings["interface"]:
1848
service.interface = (if_nametoindex
1849
(str(server_settings[u"interface"])))
1852
# Close all input and output, do double fork, etc.
2536
1855
global multiprocessing_manager
2537
1856
multiprocessing_manager = multiprocessing.Manager()
2539
1858
client_class = Client
2541
1860
client_class = functools.partial(ClientDBus, bus = bus)
2543
client_settings = Client.config_parser(client_config)
2544
old_client_settings = {}
2547
# This is used to redirect stdout and stderr for checker processes
2549
wnull = open(os.devnull, "w") # A writable /dev/null
2550
# Only used if server is running in foreground but not in debug
2552
if debug or not foreground:
2555
# Get client data and settings from last running state.
2556
if server_settings["restore"]:
2558
with open(stored_state_path, "rb") as stored_state:
2559
clients_data, old_client_settings = (pickle.load
2561
os.remove(stored_state_path)
2562
except IOError as e:
2563
if e.errno == errno.ENOENT:
2564
logger.warning("Could not load persistent state: {}"
2565
.format(os.strerror(e.errno)))
2567
logger.critical("Could not load persistent state:",
2570
except EOFError as e:
2571
logger.warning("Could not load persistent state: "
2572
"EOFError:", exc_info=e)
2574
with PGPEngine() as pgp:
2575
for client_name, client in clients_data.items():
2576
# Skip removed clients
2577
if client_name not in client_settings:
2580
# Decide which value to use after restoring saved state.
2581
# We have three different values: Old config file,
2582
# new config file, and saved state.
2583
# New config value takes precedence if it differs from old
2584
# config value, otherwise use saved state.
2585
for name, value in client_settings[client_name].items():
2587
# For each value in new config, check if it
2588
# differs from the old config value (Except for
2589
# the "secret" attribute)
2590
if (name != "secret" and
2591
value != old_client_settings[client_name]
2593
client[name] = value
2597
# Clients who has passed its expire date can still be
2598
# enabled if its last checker was successful. Clients
2599
# whose checker succeeded before we stored its state is
2600
# assumed to have successfully run all checkers during
2602
if client["enabled"]:
2603
if datetime.datetime.utcnow() >= client["expires"]:
2604
if not client["last_checked_ok"]:
2606
"disabling client {} - Client never "
2607
"performed a successful checker"
2608
.format(client_name))
2609
client["enabled"] = False
2610
elif client["last_checker_status"] != 0:
2612
"disabling client {} - Client last"
2613
" checker failed with error code {}"
2614
.format(client_name,
2615
client["last_checker_status"]))
2616
client["enabled"] = False
2618
client["expires"] = (datetime.datetime
2620
+ client["timeout"])
2621
logger.debug("Last checker succeeded,"
2622
" keeping {} enabled"
2623
.format(client_name))
1861
def client_config_items(config, section):
1862
special_settings = {
1863
"approved_by_default":
1864
lambda: config.getboolean(section,
1865
"approved_by_default"),
1867
for name, value in config.items(section):
2625
client["secret"] = (
2626
pgp.decrypt(client["encrypted_secret"],
2627
client_settings[client_name]
2630
# If decryption fails, we use secret from new settings
2631
logger.debug("Failed to decrypt {} old secret"
2632
.format(client_name))
2633
client["secret"] = (
2634
client_settings[client_name]["secret"])
2636
# Add/remove clients based on new changes made to config
2637
for client_name in (set(old_client_settings)
2638
- set(client_settings)):
2639
del clients_data[client_name]
2640
for client_name in (set(client_settings)
2641
- set(old_client_settings)):
2642
clients_data[client_name] = client_settings[client_name]
2644
# Create all client objects
2645
for client_name, client in clients_data.items():
2646
tcp_server.clients[client_name] = client_class(
2647
name = client_name, settings = client,
2648
server_settings = server_settings)
1869
yield (name, special_settings[name]())
1873
tcp_server.clients.update(set(
1874
client_class(name = section,
1875
config= dict(client_config_items(
1876
client_config, section)))
1877
for section in client_config.sections()))
2650
1878
if not tcp_server.clients:
2651
logger.warning("No clients defined")
2654
if pidfile is not None:
2658
pidfile.write("{}\n".format(pid).encode("utf-8"))
2660
logger.error("Could not write to file %r with PID %d",
1879
logger.warning(u"No clients defined")
1885
pidfile.write(str(pid) + "\n")
1888
logger.error(u"Could not write to file %r with PID %d",
1891
# "pidfile" was never created
2663
1893
del pidfilename
1895
signal.signal(signal.SIGINT, signal.SIG_IGN)
2665
1897
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2666
1898
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2669
@alternate_dbus_interfaces({"se.recompile.Mandos":
2670
"se.bsnet.fukt.Mandos"})
2671
class MandosDBusService(DBusObjectWithProperties):
1901
class MandosDBusService(dbus.service.Object):
2672
1902
"""A D-Bus proxy object"""
2673
1903
def __init__(self):
2674
dbus.service.Object.__init__(self, bus, "/")
2675
_interface = "se.recompile.Mandos"
2677
@dbus_interface_annotations(_interface)
2679
return { "org.freedesktop.DBus.Property"
2680
".EmitsChangedSignal":
2683
@dbus.service.signal(_interface, signature="o")
1904
dbus.service.Object.__init__(self, bus, u"/")
1905
_interface = u"se.bsnet.fukt.Mandos"
1907
@dbus.service.signal(_interface, signature=u"o")
2684
1908
def ClientAdded(self, objpath):
2688
@dbus.service.signal(_interface, signature="ss")
1912
@dbus.service.signal(_interface, signature=u"ss")
2689
1913
def ClientNotFound(self, fingerprint, address):
2693
@dbus.service.signal(_interface, signature="os")
1917
@dbus.service.signal(_interface, signature=u"os")
2694
1918
def ClientRemoved(self, objpath, name):
2698
@dbus.service.method(_interface, out_signature="ao")
1922
@dbus.service.method(_interface, out_signature=u"ao")
2699
1923
def GetAllClients(self):
2701
1925
return dbus.Array(c.dbus_object_path
2703
tcp_server.clients.itervalues())
1926
for c in tcp_server.clients)
2705
1928
@dbus.service.method(_interface,
2706
out_signature="a{oa{sv}}")
1929
out_signature=u"a{oa{sv}}")
2707
1930
def GetAllClientsWithProperties(self):
2709
1932
return dbus.Dictionary(
2710
((c.dbus_object_path, c.GetAll(""))
2711
for c in tcp_server.clients.itervalues()),
1933
((c.dbus_object_path, c.GetAll(u""))
1934
for c in tcp_server.clients),
1935
signature=u"oa{sv}")
2714
@dbus.service.method(_interface, in_signature="o")
1937
@dbus.service.method(_interface, in_signature=u"o")
2715
1938
def RemoveClient(self, object_path):
2717
for c in tcp_server.clients.itervalues():
1940
for c in tcp_server.clients:
2718
1941
if c.dbus_object_path == object_path:
2719
del tcp_server.clients[c.name]
1942
tcp_server.clients.remove(c)
2720
1943
c.remove_from_connection()
2721
1944
# Don't signal anything except ClientRemoved
2722
1945
c.disable(quiet=True)
2733
1956
"Cleanup function; run on exit"
2737
multiprocessing.active_children()
2739
if not (tcp_server.clients or client_settings):
2742
# Store client before exiting. Secrets are encrypted with key
2743
# based on what config file has. If config file is
2744
# removed/edited, old secret will thus be unrecovable.
2746
with PGPEngine() as pgp:
2747
for client in tcp_server.clients.itervalues():
2748
key = client_settings[client.name]["secret"]
2749
client.encrypted_secret = pgp.encrypt(client.secret,
2753
# A list of attributes that can not be pickled
2755
exclude = { "bus", "changedstate", "secret",
2756
"checker", "server_settings" }
2757
for name, typ in (inspect.getmembers
2758
(dbus.service.Object)):
2761
client_dict["encrypted_secret"] = (client
2763
for attr in client.client_structure:
2764
if attr not in exclude:
2765
client_dict[attr] = getattr(client, attr)
2767
clients[client.name] = client_dict
2768
del client_settings[client.name]["secret"]
2771
with (tempfile.NamedTemporaryFile
2772
(mode='wb', suffix=".pickle", prefix='clients-',
2773
dir=os.path.dirname(stored_state_path),
2774
delete=False)) as stored_state:
2775
pickle.dump((clients, client_settings), stored_state)
2776
tempname=stored_state.name
2777
os.rename(tempname, stored_state_path)
2778
except (IOError, OSError) as e:
2784
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2785
logger.warning("Could not save persistent state: {}"
2786
.format(os.strerror(e.errno)))
2788
logger.warning("Could not save persistent state:",
2792
# Delete all clients, and settings from config
2793
1959
while tcp_server.clients:
2794
name, client = tcp_server.clients.popitem()
1960
client = tcp_server.clients.pop()
2796
1962
client.remove_from_connection()
1963
client.disable_hook = None
2797
1964
# Don't signal anything except ClientRemoved
2798
1965
client.disable(quiet=True)
2800
1967
# Emit D-Bus signal
2801
mandos_dbus_service.ClientRemoved(client
1968
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2804
client_settings.clear()
2806
1971
atexit.register(cleanup)
2808
for client in tcp_server.clients.itervalues():
1973
for client in tcp_server.clients:
2810
1975
# Emit D-Bus signal
2811
1976
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2812
# Need to initiate checking of clients
2814
client.init_checker()
2816
1979
tcp_server.enable()
2817
1980
tcp_server.server_activate()
2819
1982
# Find out what port we got
2821
service.port = tcp_server.socket.getsockname()[1]
1983
service.port = tcp_server.socket.getsockname()[1]
2823
logger.info("Now listening on address %r, port %d,"
2824
" flowinfo %d, scope_id %d",
2825
*tcp_server.socket.getsockname())
1985
logger.info(u"Now listening on address %r, port %d,"
1986
" flowinfo %d, scope_id %d"
1987
% tcp_server.socket.getsockname())
2827
logger.info("Now listening on address %r, port %d",
2828
*tcp_server.socket.getsockname())
1989
logger.info(u"Now listening on address %r, port %d"
1990
% tcp_server.socket.getsockname())
2830
1992
#service.interface = tcp_server.socket.getsockname()[3]
2834
# From the Avahi example code
2837
except dbus.exceptions.DBusException as error:
2838
logger.critical("D-Bus Exception", exc_info=error)
2841
# End of Avahi example code
1995
# From the Avahi example code
1998
except dbus.exceptions.DBusException, error:
1999
logger.critical(u"DBusException: %s", error)
2002
# End of Avahi example code
2843
2004
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2844
2005
lambda *args, **kwargs:
2845
2006
(tcp_server.handle_request
2846
2007
(*args[2:], **kwargs) or True))
2848
logger.debug("Starting main loop")
2009
logger.debug(u"Starting main loop")
2849
2010
main_loop.run()
2850
except AvahiError as error:
2851
logger.critical("Avahi Error", exc_info=error)
2011
except AvahiError, error:
2012
logger.critical(u"AvahiError: %s", error)
2854
2015
except KeyboardInterrupt:
2856
print("", file=sys.stderr)
2857
logger.debug("Server received KeyboardInterrupt")
2858
logger.debug("Server exiting")
2018
logger.debug(u"Server received KeyboardInterrupt")
2019
logger.debug(u"Server exiting")
2859
2020
# Must run before the D-Bus bus name gets deregistered