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
409
approval_delay: datetime.timedelta(); Time to wait for approval
410
approval_duration: datetime.timedelta(); Duration of one approval
243
name: string; from the config file, used in log messages and
245
fingerprint: string (40 or 32 hexadecimal digits); used to
246
uniquely identify the client
247
secret: bytestring; sent verbatim (over TLS) to client
248
host: string; available for use by the checker command
249
created: datetime.datetime(); (UTC) object creation
250
last_enabled: datetime.datetime(); (UTC)
252
last_checked_ok: datetime.datetime(); (UTC) or None
253
timeout: datetime.timedelta(); How long from last_checked_ok
254
until this client is disabled
255
interval: datetime.timedelta(); How often to start a new checker
256
disable_hook: If set, called by disable() as disable_hook(self)
411
257
checker: subprocess.Popen(); a running checker process used
412
258
to see if the client lives.
413
259
'None' if no process is running.
414
checker_callback_tag: a gobject event source tag, or None
415
checker_command: string; External command which is run to check
416
if client lives. %() expansions are done at
260
checker_initiator_tag: a gobject event source tag, or None
261
disable_initiator_tag: - '' -
262
checker_callback_tag: - '' -
263
checker_command: string; External command which is run to check if
264
client lives. %() expansions are done at
417
265
runtime with vars(self) as dict, so that for
418
266
instance %(name)s can be used in the command.
419
checker_initiator_tag: a gobject event source tag, or None
420
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
267
current_checker_command: string; current running checker_command
424
disable_initiator_tag: a gobject event source tag, or None
426
fingerprint: string (40 or 32 hexadecimal digits); used to
427
uniquely identify the client
428
host: string; available for use by the checker command
429
interval: datetime.timedelta(); How often to start a new checker
430
last_approval_request: datetime.datetime(); (UTC) or None
431
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
436
name: string; from the config file, used in log messages and
438
secret: bytestring; sent verbatim (over TLS) to client
439
timeout: datetime.timedelta(); How long from last_checked_ok
440
until this client is disabled
441
extended_timeout: extra long timeout when secret has been sent
442
runtime_expansions: Allowed attributes for runtime expansion.
443
expires: datetime.datetime(); time (UTC) when a client will be
445
server_settings: The server_settings dict from main()
268
approval_delay: datetime.timedelta(); Time to wait for approval
269
_approved: bool(); 'None' if not yet approved/disapproved
270
approval_duration: datetime.timedelta(); Duration of one approval
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",
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):
274
def _timedelta_to_milliseconds(td):
275
"Convert a datetime.timedelta() to milliseconds"
276
return ((td.days * 24 * 60 * 60 * 1000)
277
+ (td.seconds * 1000)
278
+ (td.microseconds // 1000))
280
def timeout_milliseconds(self):
281
"Return the 'timeout' attribute in milliseconds"
282
return self._timedelta_to_milliseconds(self.timeout)
284
def interval_milliseconds(self):
285
"Return the 'interval' attribute in milliseconds"
286
return self._timedelta_to_milliseconds(self.interval)
288
def approval_delay_milliseconds(self):
289
return self._timedelta_to_milliseconds(self.approval_delay)
291
def __init__(self, name = None, disable_hook=None, config=None):
292
"""Note: the 'checker' key in 'config' sets the
293
'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)
298
logger.debug(u"Creating client %r", self.name)
531
299
# Uppercase and remove spaces from fingerprint for later
532
300
# 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
302
self.fingerprint = (config[u"fingerprint"].upper()
304
logger.debug(u" Fingerprint: %s", self.fingerprint)
305
if u"secret" in config:
306
self.secret = config[u"secret"].decode(u"base64")
307
elif u"secfile" in config:
308
with open(os.path.expanduser(os.path.expandvars
309
(config[u"secfile"])),
311
self.secret = secfile.read()
313
raise TypeError(u"No secret or secfile for client %s"
315
self.host = config.get(u"host", u"")
316
self.created = datetime.datetime.utcnow()
318
self.last_enabled = None
319
self.last_checked_ok = None
320
self.timeout = string_to_delta(config[u"timeout"])
321
self.interval = string_to_delta(config[u"interval"])
322
self.disable_hook = disable_hook
539
323
self.checker = None
540
324
self.checker_initiator_tag = None
541
325
self.disable_initiator_tag = None
542
326
self.checker_callback_tag = None
327
self.checker_command = config[u"checker"]
543
328
self.current_checker_command = None
329
self.last_connect = None
330
self._approved = None
331
self.approved_by_default = config.get(u"approved_by_default",
545
333
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)
334
self.approval_delay = string_to_delta(
335
config[u"approval_delay"])
336
self.approval_duration = string_to_delta(
337
config[u"approval_duration"])
338
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
561
# Send notice to process children that client state has changed
562
340
def send_changedstate(self):
563
with self.changedstate:
564
self.changedstate.notify_all()
341
self.changedstate.acquire()
342
self.changedstate.notify_all()
343
self.changedstate.release()
566
345
def enable(self):
567
346
"""Start this client's checker and timeout hooks"""
568
if getattr(self, "enabled", False):
347
if getattr(self, u"enabled", False):
569
348
# Already enabled
571
self.expires = datetime.datetime.utcnow() + self.timeout
350
self.send_changedstate()
573
351
self.last_enabled = datetime.datetime.utcnow()
575
self.send_changedstate()
352
# Schedule a new checker to be started an 'interval' from now,
353
# and every interval from then on.
354
self.checker_initiator_tag = (gobject.timeout_add
355
(self.interval_milliseconds(),
357
# Schedule a disable() when 'timeout' has passed
358
self.disable_initiator_tag = (gobject.timeout_add
359
(self.timeout_milliseconds(),
362
# Also start a new checker *right now*.
577
365
def disable(self, quiet=True):
578
366
"""Disable this client."""
579
367
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:
370
self.send_changedstate()
372
logger.info(u"Disabling client %s", self.name)
373
if getattr(self, u"disable_initiator_tag", False):
584
374
gobject.source_remove(self.disable_initiator_tag)
585
375
self.disable_initiator_tag = None
587
if getattr(self, "checker_initiator_tag", None) is not None:
376
if getattr(self, u"checker_initiator_tag", False):
588
377
gobject.source_remove(self.checker_initiator_tag)
589
378
self.checker_initiator_tag = None
590
379
self.stop_checker()
380
if self.disable_hook:
381
self.disable_hook(self)
591
382
self.enabled = False
593
self.send_changedstate()
594
383
# Do not run this again if called by a gobject.timeout_add
597
386
def __del__(self):
387
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
390
def checker_callback(self, pid, condition, command):
620
391
"""The checker has completed, so take appropriate actions."""
621
392
self.checker_callback_tag = None
622
393
self.checker = None
623
394
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",
395
exitstatus = os.WEXITSTATUS(condition)
397
logger.info(u"Checker for %(name)s succeeded",
628
399
self.checked_ok()
630
logger.info("Checker for %(name)s failed",
401
logger.info(u"Checker for %(name)s failed",
633
self.last_checker_status = -1
634
logger.warning("Checker for %(name)s crashed?",
404
logger.warning(u"Checker for %(name)s crashed?",
637
407
def checked_ok(self):
638
"""Assert that the client has been seen, alive and well."""
408
"""Bump up the timeout for this client.
410
This should only be called when the client has been seen,
639
413
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()
414
gobject.source_remove(self.disable_initiator_tag)
415
self.disable_initiator_tag = (gobject.timeout_add
416
(self.timeout_milliseconds(),
659
419
def start_checker(self):
660
420
"""Start a new checker subprocess if one is not running.
673
433
# If a checker exists, make sure it is not a zombie
675
435
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
676
except AttributeError:
678
except OSError as error:
679
if error.errno != errno.ECHILD:
436
except (AttributeError, OSError), error:
437
if (isinstance(error, OSError)
438
and error.errno != errno.ECHILD):
683
logger.warning("Checker was a zombie")
442
logger.warning(u"Checker was a zombie")
684
443
gobject.source_remove(self.checker_callback_tag)
685
444
self.checker_callback(pid, status,
686
445
self.current_checker_command)
687
446
# Start a new checker if needed
688
447
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
449
# In case checker_command has exactly one % operator
450
command = self.checker_command % self.host
452
# Escape attributes for the shell
453
escaped_attrs = dict((key,
454
re.escape(unicode(str(val),
458
vars(self).iteritems())
460
command = self.checker_command % escaped_attrs
461
except TypeError, error:
462
logger.error(u'Could not format string "%s":'
463
u' %s', self.checker_command, error)
464
return True # Try again later
700
465
self.current_checker_command = command
702
logger.info("Starting checker %r for %s",
467
logger.info(u"Starting checker %r for %s",
703
468
command, self.name)
704
469
# We don't need to redirect stdout and stderr, since
705
470
# in normal mode, that is already done by daemon(),
706
471
# and in debug mode we don't want to. (Stdin is
707
472
# 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
473
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.
475
shell=True, cwd=u"/")
476
self.checker_callback_tag = (gobject.child_watch_add
478
self.checker_callback,
480
# The checker may have completed before the gobject
481
# watch was added. Check for this.
731
482
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)
484
gobject.source_remove(self.checker_callback_tag)
485
self.checker_callback(pid, status, command)
486
except OSError, error:
487
logger.error(u"Failed to start subprocess: %s",
742
489
# Re-run this periodically if run by gobject.timeout_add
850
559
class DBusObjectWithProperties(dbus.service.Object):
851
560
"""A D-Bus object with properties.
853
562
Classes inheriting from this can use the dbus_service_property
854
563
decorator to expose methods as D-Bus properties. It exposes the
855
564
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),
568
def _is_dbus_property(obj):
569
return getattr(obj, u"_dbus_is_property", False)
868
def _get_all_dbus_things(self, thing):
571
def _get_all_dbus_properties(self):
869
572
"""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)))
574
return ((prop._dbus_name, prop)
576
inspect.getmembers(self, self._is_dbus_property))
879
578
def _get_dbus_property(self, interface_name, property_name):
880
579
"""Returns a bound method if one exists which is a D-Bus
881
580
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)
582
for name in (property_name,
583
property_name + u"_dbus_property"):
584
prop = getattr(self, name, None)
586
or not self._is_dbus_property(prop)
587
or prop._dbus_name != property_name
588
or (interface_name and prop._dbus_interface
589
and interface_name != prop._dbus_interface)):
891
592
# No such property
892
raise DBusPropertyNotFound(self.dbus_object_path + ":"
893
+ interface_name + "."
593
raise DBusPropertyNotFound(self.dbus_object_path + u":"
594
+ interface_name + u"."
896
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
898
599
def Get(self, interface_name, property_name):
899
600
"""Standard D-Bus property Get() method, see D-Bus standard.
901
602
prop = self._get_dbus_property(interface_name, property_name)
902
if prop._dbus_access == "write":
603
if prop._dbus_access == u"write":
903
604
raise DBusPropertyAccessException(property_name)
905
if not hasattr(value, "variant_level"):
606
if not hasattr(value, u"variant_level"):
907
608
return type(value)(value, variant_level=value.variant_level+1)
909
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
910
611
def Set(self, interface_name, property_name, value):
911
612
"""Standard D-Bus property Set() method, see D-Bus standard.
913
614
prop = self._get_dbus_property(interface_name, property_name)
914
if prop._dbus_access == "read":
615
if prop._dbus_access == u"read":
915
616
raise DBusPropertyAccessException(property_name)
916
if prop._dbus_get_args_options["byte_arrays"]:
617
if prop._dbus_get_args_options[u"byte_arrays"]:
917
618
# The byte_arrays option is not supported yet on
918
619
# 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)
620
if prop._dbus_signature != u"ay":
622
value = dbus.ByteArray(''.join(unichr(byte)
927
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
928
out_signature="a{sv}")
626
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
627
out_signature=u"a{sv}")
929
628
def GetAll(self, interface_name):
930
629
"""Standard D-Bus property GetAll() method, see D-Bus
933
632
Note: Will not include properties with access="write".
936
for name, prop in self._get_all_dbus_things("property"):
635
for name, prop in self._get_all_dbus_properties():
937
636
if (interface_name
938
637
and interface_name != prop._dbus_interface):
939
638
# Interface non-empty but did not match
941
640
# Ignore write-only properties
942
if prop._dbus_access == "write":
641
if prop._dbus_access == u"write":
945
if not hasattr(value, "variant_level"):
946
properties[name] = value
644
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")
647
all[name] = type(value)(value, variant_level=
648
value.variant_level+1)
649
return dbus.Dictionary(all, signature=u"sv")
952
651
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
954
653
path_keyword='object_path',
955
654
connection_keyword='connection')
956
655
def Introspect(self, object_path, connection):
957
"""Overloading of standard D-Bus method.
959
Inserts property tags and interface annotation tags.
656
"""Standard D-Bus method, overloaded to insert property tags.
961
658
xmlstring = dbus.service.Object.Introspect(self, object_path,
964
661
document = xml.dom.minidom.parseString(xmlstring)
965
662
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)
663
e = document.createElement(u"property")
664
e.setAttribute(u"name", name)
665
e.setAttribute(u"type", prop._dbus_signature)
666
e.setAttribute(u"access", prop._dbus_access)
971
for if_tag in document.getElementsByTagName("interface"):
668
for if_tag in document.getElementsByTagName(u"interface"):
973
669
for tag in (make_tag(document, name, prop)
975
in self._get_all_dbus_things("property")
671
in self._get_all_dbus_properties()
976
672
if prop._dbus_interface
977
== if_tag.getAttribute("name")):
673
== if_tag.getAttribute(u"name")):
978
674
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
675
# Add the names to the return values for the
1011
676
# "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")
677
if (if_tag.getAttribute(u"name")
678
== u"org.freedesktop.DBus.Properties"):
679
for cn in if_tag.getElementsByTagName(u"method"):
680
if cn.getAttribute(u"name") == u"Get":
681
for arg in cn.getElementsByTagName(u"arg"):
682
if (arg.getAttribute(u"direction")
684
arg.setAttribute(u"name", u"value")
685
elif cn.getAttribute(u"name") == u"GetAll":
686
for arg in cn.getElementsByTagName(u"arg"):
687
if (arg.getAttribute(u"direction")
689
arg.setAttribute(u"name", u"props")
690
xmlstring = document.toxml(u"utf-8")
1026
691
document.unlink()
1027
692
except (AttributeError, xml.dom.DOMException,
1028
xml.parsers.expat.ExpatError) as error:
1029
logger.error("Failed to override Introspection method",
693
xml.parsers.expat.ExpatError), error:
694
logger.error(u"Failed to override Introspection method",
1031
696
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
699
class ClientDBus(Client, DBusObjectWithProperties):
1214
700
"""A Client class using D-Bus
1217
703
dbus_object_path: dbus.ObjectPath
1218
704
bus: dbus.SystemBus()
1221
runtime_expansions = (Client.runtime_expansions
1222
+ ("dbus_object_path",))
1224
706
# dbus.service.Object doesn't use super(), so we can't either.
1226
708
def __init__(self, bus = None, *args, **kwargs):
709
self._approvals_pending = 0
1228
711
Client.__init__(self, *args, **kwargs)
1229
712
# Only now, when this client is initialized, can it show up on
1231
client_object_name = unicode(self.name).translate(
1232
{ord("."): ord("_"),
1233
ord("-"): ord("_")})
1234
714
self.dbus_object_path = (dbus.ObjectPath
1235
("/clients/" + client_object_name))
716
+ self.name.replace(u".", u"_")))
1236
717
DBusObjectWithProperties.__init__(self, self.bus,
1237
718
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
720
def _get_approvals_pending(self):
721
return self._approvals_pending
722
def _set_approvals_pending(self, value):
723
old_value = self._approvals_pending
724
self._approvals_pending = value
726
if (hasattr(self, "dbus_object_path")
727
and bval is not bool(old_value)):
728
dbus_bool = dbus.Boolean(bval, variant_level=1)
729
self.PropertyChanged(dbus.String(u"ApprovalPending"),
732
approvals_pending = property(_get_approvals_pending,
733
_set_approvals_pending)
734
del _get_approvals_pending, _set_approvals_pending
737
def _datetime_to_dbus(dt, variant_level=0):
738
"""Convert a UTC datetime.datetime() to a D-Bus type."""
739
return dbus.String(dt.isoformat(),
740
variant_level=variant_level)
743
oldstate = getattr(self, u"enabled", False)
744
r = Client.enable(self)
745
if oldstate != self.enabled:
747
self.PropertyChanged(dbus.String(u"Enabled"),
748
dbus.Boolean(True, variant_level=1))
749
self.PropertyChanged(
750
dbus.String(u"LastEnabled"),
751
self._datetime_to_dbus(self.last_enabled,
755
def disable(self, quiet = False):
756
oldstate = getattr(self, u"enabled", False)
757
r = Client.disable(self, quiet=quiet)
758
if not quiet and oldstate != self.enabled:
760
self.PropertyChanged(dbus.String(u"Enabled"),
761
dbus.Boolean(False, variant_level=1))
1309
764
def __del__(self, *args, **kwargs):
1311
766
self.remove_from_connection()
1312
767
except LookupError:
1314
if hasattr(DBusObjectWithProperties, "__del__"):
769
if hasattr(DBusObjectWithProperties, u"__del__"):
1315
770
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1316
771
Client.__del__(self, *args, **kwargs)
1445
923
# ApprovalPending - property
1446
@dbus_service_property(_interface, signature="b", access="read")
924
@dbus_service_property(_interface, signature=u"b", access=u"read")
1447
925
def ApprovalPending_dbus_property(self):
1448
926
return dbus.Boolean(bool(self.approvals_pending))
1450
928
# ApprovedByDefault - property
1451
@dbus_service_property(_interface, signature="b",
929
@dbus_service_property(_interface, signature=u"b",
1453
931
def ApprovedByDefault_dbus_property(self, value=None):
1454
932
if value is None: # get
1455
933
return dbus.Boolean(self.approved_by_default)
1456
934
self.approved_by_default = bool(value)
936
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
937
dbus.Boolean(value, variant_level=1))
1458
939
# ApprovalDelay - property
1459
@dbus_service_property(_interface, signature="t",
940
@dbus_service_property(_interface, signature=u"t",
1461
942
def ApprovalDelay_dbus_property(self, value=None):
1462
943
if value is None: # get
1463
return dbus.UInt64(self.approval_delay.total_seconds()
944
return dbus.UInt64(self.approval_delay_milliseconds())
1465
945
self.approval_delay = datetime.timedelta(0, 0, 0, value)
947
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
948
dbus.UInt64(value, variant_level=1))
1467
950
# ApprovalDuration - property
1468
@dbus_service_property(_interface, signature="t",
951
@dbus_service_property(_interface, signature=u"t",
1470
953
def ApprovalDuration_dbus_property(self, value=None):
1471
954
if value is None: # get
1472
return dbus.UInt64(self.approval_duration.total_seconds()
955
return dbus.UInt64(self._timedelta_to_milliseconds(
956
self.approval_duration))
1474
957
self.approval_duration = datetime.timedelta(0, 0, 0, value)
959
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
960
dbus.UInt64(value, variant_level=1))
1476
962
# Name - property
1477
@dbus_service_property(_interface, signature="s", access="read")
963
@dbus_service_property(_interface, signature=u"s", access=u"read")
1478
964
def Name_dbus_property(self):
1479
965
return dbus.String(self.name)
1481
967
# Fingerprint - property
1482
@dbus_service_property(_interface, signature="s", access="read")
968
@dbus_service_property(_interface, signature=u"s", access=u"read")
1483
969
def Fingerprint_dbus_property(self):
1484
970
return dbus.String(self.fingerprint)
1486
972
# Host - property
1487
@dbus_service_property(_interface, signature="s",
973
@dbus_service_property(_interface, signature=u"s",
1489
975
def Host_dbus_property(self, value=None):
1490
976
if value is None: # get
1491
977
return dbus.String(self.host)
1492
self.host = unicode(value)
980
self.PropertyChanged(dbus.String(u"Host"),
981
dbus.String(value, variant_level=1))
1494
983
# Created - property
1495
@dbus_service_property(_interface, signature="s", access="read")
984
@dbus_service_property(_interface, signature=u"s", access=u"read")
1496
985
def Created_dbus_property(self):
1497
return datetime_to_dbus(self.created)
986
return dbus.String(self._datetime_to_dbus(self.created))
1499
988
# LastEnabled - property
1500
@dbus_service_property(_interface, signature="s", access="read")
989
@dbus_service_property(_interface, signature=u"s", access=u"read")
1501
990
def LastEnabled_dbus_property(self):
1502
return datetime_to_dbus(self.last_enabled)
991
if self.last_enabled is None:
992
return dbus.String(u"")
993
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1504
995
# Enabled - property
1505
@dbus_service_property(_interface, signature="b",
996
@dbus_service_property(_interface, signature=u"b",
1507
998
def Enabled_dbus_property(self, value=None):
1508
999
if value is None: # get
1509
1000
return dbus.Boolean(self.enabled)
1515
1006
# LastCheckedOK - property
1516
@dbus_service_property(_interface, signature="s",
1007
@dbus_service_property(_interface, signature=u"s",
1008
access=u"readwrite")
1518
1009
def LastCheckedOK_dbus_property(self, value=None):
1519
1010
if value is not None:
1520
1011
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)
1013
if self.last_checked_ok is None:
1014
return dbus.String(u"")
1015
return dbus.String(self._datetime_to_dbus(self
1540
1018
# Timeout - property
1541
@dbus_service_property(_interface, signature="t",
1019
@dbus_service_property(_interface, signature=u"t",
1020
access=u"readwrite")
1543
1021
def Timeout_dbus_property(self, value=None):
1544
1022
if value is None: # get
1545
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1546
old_timeout = self.timeout
1023
return dbus.UInt64(self.timeout_milliseconds())
1547
1024
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)
1026
self.PropertyChanged(dbus.String(u"Timeout"),
1027
dbus.UInt64(value, variant_level=1))
1028
if getattr(self, u"disable_initiator_tag", None) is None:
1030
# Reschedule timeout
1031
gobject.source_remove(self.disable_initiator_tag)
1032
self.disable_initiator_tag = None
1033
time_to_die = (self.
1034
_timedelta_to_milliseconds((self
1039
if time_to_die <= 0:
1040
# The timeout has passed
1043
self.disable_initiator_tag = (gobject.timeout_add
1044
(time_to_die, self.disable))
1574
1046
# Interval - property
1575
@dbus_service_property(_interface, signature="t",
1047
@dbus_service_property(_interface, signature=u"t",
1048
access=u"readwrite")
1577
1049
def Interval_dbus_property(self, value=None):
1578
1050
if value is None: # get
1579
return dbus.UInt64(self.interval.total_seconds() * 1000)
1051
return dbus.UInt64(self.interval_milliseconds())
1580
1052
self.interval = datetime.timedelta(0, 0, 0, value)
1581
if getattr(self, "checker_initiator_tag", None) is None:
1054
self.PropertyChanged(dbus.String(u"Interval"),
1055
dbus.UInt64(value, variant_level=1))
1056
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
1058
# Reschedule checker run
1059
gobject.source_remove(self.checker_initiator_tag)
1060
self.checker_initiator_tag = (gobject.timeout_add
1061
(value, self.start_checker))
1062
self.start_checker() # Start one now, too
1590
1064
# Checker - property
1591
@dbus_service_property(_interface, signature="s",
1065
@dbus_service_property(_interface, signature=u"s",
1066
access=u"readwrite")
1593
1067
def Checker_dbus_property(self, value=None):
1594
1068
if value is None: # get
1595
1069
return dbus.String(self.checker_command)
1596
self.checker_command = unicode(value)
1070
self.checker_command = value
1072
self.PropertyChanged(dbus.String(u"Checker"),
1073
dbus.String(self.checker_command,
1598
1076
# CheckerRunning - property
1599
@dbus_service_property(_interface, signature="b",
1077
@dbus_service_property(_interface, signature=u"b",
1078
access=u"readwrite")
1601
1079
def CheckerRunning_dbus_property(self, value=None):
1602
1080
if value is None: # get
1603
1081
return dbus.Boolean(self.checker is not None)
1655
1133
def handle(self):
1656
1134
with contextlib.closing(self.server.child_pipe) as child_pipe:
1657
logger.info("TCP connection from: %s",
1135
logger.info(u"TCP connection from: %s",
1658
1136
unicode(self.client_address))
1659
logger.debug("Pipe FD: %d",
1137
logger.debug(u"Pipe FD: %d",
1660
1138
self.server.child_pipe.fileno())
1662
1140
session = (gnutls.connection
1663
1141
.ClientSession(self.request,
1664
1142
gnutls.connection
1665
1143
.X509Credentials()))
1667
1145
# Note: gnutls.connection.X509Credentials is really a
1668
1146
# generic GnuTLS certificate credentials object so long as
1669
1147
# no X.509 keys are added to it. Therefore, we can use it
1670
1148
# here despite using OpenPGP certificates.
1672
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1673
# "+AES-256-CBC", "+SHA1",
1674
# "+COMP-NULL", "+CTYPE-OPENPGP",
1150
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1151
# u"+AES-256-CBC", u"+SHA1",
1152
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1676
1154
# Use a fallback default, since this MUST be set.
1677
1155
priority = self.server.gnutls_priority
1678
1156
if priority is None:
1157
priority = u"NORMAL"
1680
1158
(gnutls.library.functions
1681
1159
.gnutls_priority_set_direct(session._c_object,
1682
1160
priority, None))
1684
1162
# Start communication using the Mandos protocol
1685
1163
# Get protocol number
1686
1164
line = self.request.makefile().readline()
1687
logger.debug("Protocol version: %r", line)
1165
logger.debug(u"Protocol version: %r", line)
1689
1167
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)
1169
except (ValueError, IndexError, RuntimeError), error:
1170
logger.error(u"Unknown protocol version: %s", error)
1695
1173
# Start GnuTLS connection
1697
1175
session.handshake()
1698
except gnutls.errors.GNUTLSError as error:
1699
logger.warning("Handshake failed: %s", error)
1176
except gnutls.errors.GNUTLSError, error:
1177
logger.warning(u"Handshake failed: %s", error)
1700
1178
# Do not run session.bye() here: the session is not
1701
1179
# established. Just abandon the request.
1703
logger.debug("Handshake succeeded")
1181
logger.debug(u"Handshake succeeded")
1705
1183
approval_required = False
1708
1186
fpr = self.fingerprint(self.peer_certificate
1711
gnutls.errors.GNUTLSError) as error:
1712
logger.warning("Bad certificate: %s", error)
1188
except (TypeError, gnutls.errors.GNUTLSError), error:
1189
logger.warning(u"Bad certificate: %s", error)
1714
logger.debug("Fingerprint: %s", fpr)
1191
logger.debug(u"Fingerprint: %s", fpr)
1717
1194
client = ProxyClient(child_pipe, fpr,
1718
1195
self.client_address)
1911
1383
use_ipv6: Boolean; to use IPv6 or not
1913
1385
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().
1386
interface=None, use_ipv6=True):
1918
1387
self.interface = interface
1920
1389
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
1390
socketserver.TCPServer.__init__(self, server_address,
1948
1391
RequestHandlerClass)
1950
1392
def server_bind(self):
1951
1393
"""This overrides the normal server_bind() function
1952
1394
to bind to an interface if one was specified, and also NOT to
1953
1395
bind to an address or port if they were not specified."""
1954
1396
if self.interface is not None:
1955
1397
if SO_BINDTODEVICE is None:
1956
logger.error("SO_BINDTODEVICE does not exist;"
1957
" cannot bind to interface %s",
1398
logger.error(u"SO_BINDTODEVICE does not exist;"
1399
u" cannot bind to interface %s",
1958
1400
self.interface)
1961
1403
self.socket.setsockopt(socket.SOL_SOCKET,
1962
1404
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)
1407
except socket.error, error:
1408
if error[0] == errno.EPERM:
1409
logger.error(u"No permission to"
1410
u" bind to interface %s",
1412
elif error[0] == errno.ENOPROTOOPT:
1413
logger.error(u"SO_BINDTODEVICE not available;"
1414
u" cannot bind to interface %s",
1977
1418
# Only bind(2) the socket if we really need to.
1978
1419
if self.server_address[0] or self.server_address[1]:
1979
1420
if not self.server_address[0]:
1980
1421
if self.address_family == socket.AF_INET6:
1981
any_address = "::" # in6addr_any
1422
any_address = u"::" # in6addr_any
1983
any_address = "0.0.0.0" # INADDR_ANY
1424
any_address = socket.INADDR_ANY
1984
1425
self.server_address = (any_address,
1985
1426
self.server_address[1])
1986
1427
elif not self.server_address[1]:
2008
1449
def __init__(self, server_address, RequestHandlerClass,
2009
1450
interface=None, use_ipv6=True, clients=None,
2010
gnutls_priority=None, use_dbus=True, socketfd=None):
1451
gnutls_priority=None, use_dbus=True):
2011
1452
self.enabled = False
2012
1453
self.clients = clients
2013
1454
if self.clients is None:
1455
self.clients = set()
2015
1456
self.use_dbus = use_dbus
2016
1457
self.gnutls_priority = gnutls_priority
2017
1458
IPv6_TCPServer.__init__(self, server_address,
2018
1459
RequestHandlerClass,
2019
1460
interface = interface,
2020
use_ipv6 = use_ipv6,
2021
socketfd = socketfd)
1461
use_ipv6 = use_ipv6)
2022
1462
def server_activate(self):
2023
1463
if self.enabled:
2024
1464
return socketserver.TCPServer.server_activate(self)
2026
1465
def enable(self):
2027
1466
self.enabled = True
2029
def add_pipe(self, parent_pipe, proc):
1467
def add_pipe(self, parent_pipe):
2030
1468
# Call "handle_ipc" for both data and EOF events
2031
1469
gobject.io_add_watch(parent_pipe.fileno(),
2032
1470
gobject.IO_IN | gobject.IO_HUP,
2033
1471
functools.partial(self.handle_ipc,
1472
parent_pipe = parent_pipe))
2038
1474
def handle_ipc(self, source, condition, parent_pipe=None,
2039
proc = None, client_object=None):
2040
# error, or the other end of multiprocessing.Pipe has closed
2041
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2042
# Wait for other process to exit
1475
client_object=None):
1477
gobject.IO_IN: u"IN", # There is data to read.
1478
gobject.IO_OUT: u"OUT", # Data can be written (without
1480
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1481
gobject.IO_ERR: u"ERR", # Error condition.
1482
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1483
# broken, usually for pipes and
1486
conditions_string = ' | '.join(name
1488
condition_names.iteritems()
1489
if cond & condition)
1490
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1493
# error or the other end of multiprocessing.Pipe has closed
1494
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
2046
1497
# Read a request from the child
2047
1498
request = parent_pipe.recv()
1499
logger.debug(u"IPC request: %s", repr(request))
2048
1500
command = request[0]
2050
1502
if command == 'init':
2051
1503
fpr = request[1]
2052
1504
address = request[2]
2054
for c in self.clients.itervalues():
1506
for c in self.clients:
2055
1507
if c.fingerprint == fpr:
2059
logger.info("Client not found for fingerprint: %s, ad"
2060
"dress: %s", fpr, address)
1511
logger.warning(u"Client not found for fingerprint: %s, ad"
1512
u"dress: %s", fpr, address)
2061
1513
if self.use_dbus:
2062
1514
# Emit D-Bus signal
2063
mandos_dbus_service.ClientNotFound(fpr,
1515
mandos_dbus_service.ClientNotFound(fpr, address)
2065
1516
parent_pipe.send(False)
2068
1519
gobject.io_add_watch(parent_pipe.fileno(),
2069
1520
gobject.IO_IN | gobject.IO_HUP,
2070
1521
functools.partial(self.handle_ipc,
1522
parent_pipe = parent_pipe,
1523
client_object = client))
2076
1524
parent_pipe.send(True)
2077
# remove the old hook in favor of the new above hook on
1525
# remove the old hook in favor of the new above hook on same fileno
2080
1527
if command == 'funcall':
2081
1528
funcname = request[1]
2082
1529
args = request[2]
2083
1530
kwargs = request[3]
2085
parent_pipe.send(('data', getattr(client_object,
1532
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
2089
1534
if command == 'getattr':
2090
1535
attrname = request[1]
2091
1536
if callable(client_object.__getattribute__(attrname)):
2092
1537
parent_pipe.send(('function',))
2094
parent_pipe.send(('data', client_object
2095
.__getattribute__(attrname)))
1539
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
2097
1541
if command == 'setattr':
2098
1542
attrname = request[1]
2099
1543
value = request[2]
2100
1544
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
1549
def string_to_delta(interval):
2205
1550
"""Parse a string and return a datetime.timedelta
2207
>>> string_to_delta('7d')
1552
>>> string_to_delta(u'7d')
2208
1553
datetime.timedelta(7)
2209
>>> string_to_delta('60s')
1554
>>> string_to_delta(u'60s')
2210
1555
datetime.timedelta(0, 60)
2211
>>> string_to_delta('60m')
1556
>>> string_to_delta(u'60m')
2212
1557
datetime.timedelta(0, 3600)
2213
>>> string_to_delta('24h')
1558
>>> string_to_delta(u'24h')
2214
1559
datetime.timedelta(1)
2215
>>> string_to_delta('1w')
1560
>>> string_to_delta(u'1w')
2216
1561
datetime.timedelta(7)
2217
>>> string_to_delta('5m 30s')
1562
>>> string_to_delta(u'5m 30s')
2218
1563
datetime.timedelta(0, 330)
2222
return rfc3339_duration_to_delta(interval)
2226
1565
timevalue = datetime.timedelta(0)
2227
1566
for s in interval.split():
2229
1568
suffix = unicode(s[-1])
2230
1569
value = int(s[:-1])
2232
1571
delta = datetime.timedelta(value)
1572
elif suffix == u"s":
2234
1573
delta = datetime.timedelta(0, value)
1574
elif suffix == u"m":
2236
1575
delta = datetime.timedelta(0, 0, 0, 0, value)
1576
elif suffix == u"h":
2238
1577
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1578
elif suffix == u"w":
2240
1579
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))
1581
raise ValueError(u"Unknown suffix %r" % suffix)
1582
except (ValueError, IndexError), e:
1583
raise ValueError(e.message)
2246
1584
timevalue += delta
2247
1585
return timevalue
1588
def if_nametoindex(interface):
1589
"""Call the C function if_nametoindex(), or equivalent
1591
Note: This function cannot accept a unicode string."""
1592
global if_nametoindex
1594
if_nametoindex = (ctypes.cdll.LoadLibrary
1595
(ctypes.util.find_library(u"c"))
1597
except (OSError, AttributeError):
1598
logger.warning(u"Doing if_nametoindex the hard way")
1599
def if_nametoindex(interface):
1600
"Get an interface index the hard way, i.e. using fcntl()"
1601
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1602
with contextlib.closing(socket.socket()) as s:
1603
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1604
struct.pack(str(u"16s16x"),
1606
interface_index = struct.unpack(str(u"I"),
1608
return interface_index
1609
return if_nametoindex(interface)
2250
1612
def daemon(nochdir = False, noclose = False):
2251
1613
"""See daemon(3). Standard BSD Unix function.
2276
1639
##################################################################
2277
1640
# 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()
1642
parser = optparse.OptionParser(version = "%%prog %s" % version)
1643
parser.add_option("-i", u"--interface", type=u"string",
1644
metavar="IF", help=u"Bind to interface IF")
1645
parser.add_option("-a", u"--address", type=u"string",
1646
help=u"Address to listen for requests on")
1647
parser.add_option("-p", u"--port", type=u"int",
1648
help=u"Port number to receive requests on")
1649
parser.add_option("--check", action=u"store_true",
1650
help=u"Run self-test")
1651
parser.add_option("--debug", action=u"store_true",
1652
help=u"Debug mode; run in foreground and log to"
1654
parser.add_option("--debuglevel", type=u"string", metavar="Level",
1655
help=u"Debug level for stdout output")
1656
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1657
u" priority string (see GnuTLS documentation)")
1658
parser.add_option("--servicename", type=u"string",
1659
metavar=u"NAME", help=u"Zeroconf service name")
1660
parser.add_option("--configdir", type=u"string",
1661
default=u"/etc/mandos", metavar=u"DIR",
1662
help=u"Directory to search for configuration"
1664
parser.add_option("--no-dbus", action=u"store_false",
1665
dest=u"use_dbus", help=u"Do not provide D-Bus"
1666
u" system bus interface")
1667
parser.add_option("--no-ipv6", action=u"store_false",
1668
dest=u"use_ipv6", help=u"Do not use IPv6")
1669
options = parser.parse_args()[0]
2326
1671
if options.check:
2328
fail_count, test_count = doctest.testmod()
2329
sys.exit(os.EX_OK if fail_count == 0 else 1)
2331
1676
# 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",
1677
server_defaults = { u"interface": u"",
1682
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1683
u"servicename": u"Mandos",
1684
u"use_dbus": u"True",
1685
u"use_ipv6": u"True",
2349
1689
# Parse config file for server-global settings
2350
1690
server_config = configparser.SafeConfigParser(server_defaults)
2351
1691
del server_defaults
2352
1692
server_config.read(os.path.join(options.configdir,
2354
1694
# Convert the SafeConfigParser object to a dict
2355
1695
server_settings = server_config.defaults()
2356
1696
# 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",
1697
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1698
server_settings[option] = server_config.getboolean(u"DEFAULT",
2360
1700
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
1701
server_settings["port"] = server_config.getint(u"DEFAULT",
2372
1703
del server_config
2374
1705
# Override the settings from the config file with command line
2375
1706
# 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"):
1707
for option in (u"interface", u"address", u"port", u"debug",
1708
u"priority", u"servicename", u"configdir",
1709
u"use_dbus", u"use_ipv6", u"debuglevel"):
2380
1710
value = getattr(options, option)
2381
1711
if value is not None:
2382
1712
server_settings[option] = value
2385
1715
for option in server_settings.keys():
2386
1716
if type(server_settings[option]) is str:
2387
1717
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
1718
# Now we have our good server settings in "server_settings"
2397
1720
##################################################################
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
1722
# 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":
1723
debug = server_settings[u"debug"]
1724
debuglevel = server_settings[u"debuglevel"]
1725
use_dbus = server_settings[u"use_dbus"]
1726
use_ipv6 = server_settings[u"use_ipv6"]
1728
if server_settings[u"servicename"] != u"Mandos":
2425
1729
syslogger.setFormatter(logging.Formatter
2426
('Mandos ({}) [%(process)d]:'
2427
' %(levelname)s: %(message)s'
2428
.format(server_settings
1730
(u'Mandos (%s) [%%(process)d]:'
1731
u' %%(levelname)s: %%(message)s'
1732
% server_settings[u"servicename"]))
2431
1734
# Parse config file with clients
2432
client_config = configparser.SafeConfigParser(Client
2434
client_config.read(os.path.join(server_settings["configdir"],
1735
client_defaults = { u"timeout": u"1h",
1737
u"checker": u"fping -q -- %%(host)s",
1739
u"approval_delay": u"0s",
1740
u"approval_duration": u"1s",
1742
client_config = configparser.SafeConfigParser(client_defaults)
1743
client_config.read(os.path.join(server_settings[u"configdir"],
2437
1746
global mandos_dbus_service
2438
1747
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"]),
1749
tcp_server = MandosServer((server_settings[u"address"],
1750
server_settings[u"port"]),
2446
interface=(server_settings["interface"]
1752
interface=(server_settings[u"interface"]
2448
1754
use_ipv6=use_ipv6,
2449
1755
gnutls_priority=
2450
server_settings["priority"],
2454
pidfilename = "/run/mandos.pid"
2455
if not os.path.isdir("/run/."):
2456
pidfilename = "/var/run/mandos.pid"
2459
pidfile = open(pidfilename, "w")
2460
except IOError as e:
2461
logger.error("Could not open file %r", pidfilename,
1756
server_settings[u"priority"],
1758
pidfilename = u"/var/run/mandos.pid"
1760
pidfile = open(pidfilename, u"w")
1762
logger.error(u"Could not open file %r", pidfilename)
2464
for name in ("_mandos", "mandos", "nobody"):
1765
uid = pwd.getpwnam(u"_mandos").pw_uid
1766
gid = pwd.getpwnam(u"_mandos").pw_gid
2466
uid = pwd.getpwnam(name).pw_uid
2467
gid = pwd.getpwnam(name).pw_gid
1769
uid = pwd.getpwnam(u"mandos").pw_uid
1770
gid = pwd.getpwnam(u"mandos").pw_gid
2469
1771
except KeyError:
1773
uid = pwd.getpwnam(u"nobody").pw_uid
1774
gid = pwd.getpwnam(u"nobody").pw_gid
2477
except OSError as error:
2478
if error.errno != errno.EPERM:
1781
except OSError, error:
1782
if error[0] != errno.EPERM:
1785
if not debug and not debuglevel:
1786
syslogger.setLevel(logging.WARNING)
1787
console.setLevel(logging.WARNING)
1789
level = getattr(logging, debuglevel.upper())
1790
syslogger.setLevel(level)
1791
console.setLevel(level)
2482
1794
# Enable all possible GnuTLS debugging
2488
1800
@gnutls.library.types.gnutls_log_func
2489
1801
def debug_gnutls(level, string):
2490
logger.debug("GnuTLS: %s", string[:-1])
1802
logger.debug(u"GnuTLS: %s", string[:-1])
2492
1804
(gnutls.library.functions
2493
1805
.gnutls_global_set_log_function(debug_gnutls))
2495
1807
# Redirect stdin so all checkers get /dev/null
2496
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1808
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2497
1809
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()
1813
# No console logging
1814
logger.removeHandler(console)
2510
1817
global main_loop
2511
1818
# From the Avahi example code
2512
DBusGMainLoop(set_as_default=True)
1819
DBusGMainLoop(set_as_default=True )
2513
1820
main_loop = gobject.MainLoop()
2514
1821
bus = dbus.SystemBus()
2515
1822
# End of Avahi example code
2518
bus_name = dbus.service.BusName("se.recompile.Mandos",
1825
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2519
1826
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)
1827
except dbus.exceptions.NameExistsException, e:
1828
logger.error(unicode(e) + u", disabling D-Bus")
2525
1829
use_dbus = False
2526
server_settings["use_dbus"] = False
1830
server_settings[u"use_dbus"] = False
2527
1831
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"])))
1832
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1833
service = AvahiService(name = server_settings[u"servicename"],
1834
servicetype = u"_mandos._tcp",
1835
protocol = protocol, bus = bus)
1836
if server_settings["interface"]:
1837
service.interface = (if_nametoindex
1838
(str(server_settings[u"interface"])))
1841
# Close all input and output, do double fork, etc.
2538
1844
global multiprocessing_manager
2539
1845
multiprocessing_manager = multiprocessing.Manager()
2541
1847
client_class = Client
2543
1849
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))
1850
def client_config_items(config, section):
1851
special_settings = {
1852
"approved_by_default":
1853
lambda: config.getboolean(section,
1854
"approved_by_default"),
1856
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)
1858
yield (name, special_settings[name]())
1862
tcp_server.clients.update(set(
1863
client_class(name = section,
1864
config= dict(client_config_items(
1865
client_config, section)))
1866
for section in client_config.sections()))
2652
1867
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",
1868
logger.warning(u"No clients defined")
1873
pidfile.write(str(pid) + "\n")
1876
logger.error(u"Could not write to file %r with PID %d",
1879
# "pidfile" was never created
1884
signal.signal(signal.SIGINT, signal.SIG_IGN)
2667
1885
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2668
1886
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):
1889
class MandosDBusService(dbus.service.Object):
2674
1890
"""A D-Bus proxy object"""
2675
1891
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")
1892
dbus.service.Object.__init__(self, bus, u"/")
1893
_interface = u"se.bsnet.fukt.Mandos"
1895
@dbus.service.signal(_interface, signature=u"o")
2686
1896
def ClientAdded(self, objpath):
2690
@dbus.service.signal(_interface, signature="ss")
1900
@dbus.service.signal(_interface, signature=u"ss")
2691
1901
def ClientNotFound(self, fingerprint, address):
2695
@dbus.service.signal(_interface, signature="os")
1905
@dbus.service.signal(_interface, signature=u"os")
2696
1906
def ClientRemoved(self, objpath, name):
2700
@dbus.service.method(_interface, out_signature="ao")
1910
@dbus.service.method(_interface, out_signature=u"ao")
2701
1911
def GetAllClients(self):
2703
1913
return dbus.Array(c.dbus_object_path
2705
tcp_server.clients.itervalues())
1914
for c in tcp_server.clients)
2707
1916
@dbus.service.method(_interface,
2708
out_signature="a{oa{sv}}")
1917
out_signature=u"a{oa{sv}}")
2709
1918
def GetAllClientsWithProperties(self):
2711
1920
return dbus.Dictionary(
2712
((c.dbus_object_path, c.GetAll(""))
2713
for c in tcp_server.clients.itervalues()),
1921
((c.dbus_object_path, c.GetAll(u""))
1922
for c in tcp_server.clients),
1923
signature=u"oa{sv}")
2716
@dbus.service.method(_interface, in_signature="o")
1925
@dbus.service.method(_interface, in_signature=u"o")
2717
1926
def RemoveClient(self, object_path):
2719
for c in tcp_server.clients.itervalues():
1928
for c in tcp_server.clients:
2720
1929
if c.dbus_object_path == object_path:
2721
del tcp_server.clients[c.name]
1930
tcp_server.clients.remove(c)
2722
1931
c.remove_from_connection()
2723
1932
# Don't signal anything except ClientRemoved
2724
1933
c.disable(quiet=True)
2735
1944
"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
1947
while tcp_server.clients:
2796
name, client = tcp_server.clients.popitem()
1948
client = tcp_server.clients.pop()
2798
1950
client.remove_from_connection()
1951
client.disable_hook = None
2799
1952
# Don't signal anything except ClientRemoved
2800
1953
client.disable(quiet=True)
2802
1955
# Emit D-Bus signal
2803
mandos_dbus_service.ClientRemoved(client
1956
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2806
client_settings.clear()
2808
1959
atexit.register(cleanup)
2810
for client in tcp_server.clients.itervalues():
1961
for client in tcp_server.clients:
2812
1963
# Emit D-Bus signal
2813
1964
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2814
# Need to initiate checking of clients
2816
client.init_checker()
2818
1967
tcp_server.enable()
2819
1968
tcp_server.server_activate()
2821
1970
# Find out what port we got
2823
service.port = tcp_server.socket.getsockname()[1]
1971
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())
1973
logger.info(u"Now listening on address %r, port %d,"
1974
" flowinfo %d, scope_id %d"
1975
% tcp_server.socket.getsockname())
2829
logger.info("Now listening on address %r, port %d",
2830
*tcp_server.socket.getsockname())
1977
logger.info(u"Now listening on address %r, port %d"
1978
% tcp_server.socket.getsockname())
2832
1980
#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
1983
# From the Avahi example code
1986
except dbus.exceptions.DBusException, error:
1987
logger.critical(u"DBusException: %s", error)
1990
# End of Avahi example code
2845
1992
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2846
1993
lambda *args, **kwargs:
2847
1994
(tcp_server.handle_request
2848
1995
(*args[2:], **kwargs) or True))
2850
logger.debug("Starting main loop")
1997
logger.debug(u"Starting main loop")
2851
1998
main_loop.run()
2852
except AvahiError as error:
2853
logger.critical("Avahi Error", exc_info=error)
1999
except AvahiError, error:
2000
logger.critical(u"AvahiError: %s", error)
2856
2003
except KeyboardInterrupt:
2858
print("", file=sys.stderr)
2859
logger.debug("Server received KeyboardInterrupt")
2860
logger.debug("Server exiting")
2006
logger.debug(u"Server received KeyboardInterrupt")
2007
logger.debug(u"Server exiting")
2861
2008
# Must run before the D-Bus bus name gets deregistered