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