88
76
except ImportError:
89
77
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
82
logger = logging.Logger(u'mandos')
83
syslogger = (logging.handlers.SysLogHandler
84
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
85
address = "/dev/log"))
86
syslogger.setFormatter(logging.Formatter
87
(u'Mandos [%(process)d]: %(levelname)s:'
89
logger.addHandler(syslogger)
91
console = logging.StreamHandler()
92
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
95
logger.addHandler(console)
224
97
class AvahiError(Exception):
225
98
def __init__(self, value, *args, **kwargs):
325
186
dbus.UInt16(self.port),
326
187
avahi.string_array_to_txt_array(self.TXT))
327
188
self.group.Commit()
329
189
def entry_group_state_changed(self, state, error):
330
190
"""Derived from the Avahi example code"""
331
logger.debug("Avahi entry group state change: %i", state)
191
logger.debug(u"Avahi state change: %i", state)
333
193
if state == avahi.ENTRY_GROUP_ESTABLISHED:
334
logger.debug("Zeroconf service established.")
194
logger.debug(u"Zeroconf service established.")
335
195
elif state == avahi.ENTRY_GROUP_COLLISION:
336
logger.info("Zeroconf service name collision.")
196
logger.warning(u"Zeroconf service name collision.")
338
198
elif state == avahi.ENTRY_GROUP_FAILURE:
339
logger.critical("Avahi: Error in group state changed %s",
199
logger.critical(u"Avahi: Error in group state changed %s",
341
raise AvahiGroupError("State changed: {!s}"
201
raise AvahiGroupError(u"State changed: %s"
344
203
def cleanup(self):
345
204
"""Derived from the Avahi example code"""
346
205
if self.group is not None:
349
except (dbus.exceptions.UnknownMethodException,
350
dbus.exceptions.DBusException):
352
207
self.group = None
355
def server_state_changed(self, state, error=None):
208
def server_state_changed(self, state):
356
209
"""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)
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
372
213
elif state == avahi.SERVER_RUNNING:
376
logger.debug("Unknown state: %r", state)
378
logger.debug("Unknown state: %r: %r", state, error)
380
215
def activate(self):
381
216
"""Derived from the Avahi example code"""
382
217
if self.server is None:
383
218
self.server = dbus.Interface(
384
219
self.bus.get_object(avahi.DBUS_NAME,
385
avahi.DBUS_PATH_SERVER,
386
follow_name_owner_changes=True),
220
avahi.DBUS_PATH_SERVER),
387
221
avahi.DBUS_INTERFACE_SERVER)
388
self.server.connect_to_signal("StateChanged",
222
self.server.connect_to_signal(u"StateChanged",
389
223
self.server_state_changed)
390
224
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
227
class Client(object):
405
228
"""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
231
name: string; from the config file, used in log messages and
233
fingerprint: string (40 or 32 hexadecimal digits); used to
234
uniquely identify the client
235
secret: bytestring; sent verbatim (over TLS) to client
236
host: string; available for use by the checker command
237
created: datetime.datetime(); (UTC) object creation
238
last_enabled: datetime.datetime(); (UTC)
240
last_checked_ok: datetime.datetime(); (UTC) or None
241
timeout: datetime.timedelta(); How long from last_checked_ok
242
until this client is invalid
243
interval: datetime.timedelta(); How often to start a new checker
244
disable_hook: If set, called by disable() as disable_hook(self)
411
245
checker: subprocess.Popen(); a running checker process used
412
246
to see if the client lives.
413
247
'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
248
checker_initiator_tag: a gobject event source tag, or None
249
disable_initiator_tag: - '' -
250
checker_callback_tag: - '' -
251
checker_command: string; External command which is run to check if
252
client lives. %() expansions are done at
417
253
runtime with vars(self) as dict, so that for
418
254
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
255
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()
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):
259
def _datetime_to_milliseconds(dt):
260
"Convert a datetime.datetime() to milliseconds"
261
return ((dt.days * 24 * 60 * 60 * 1000)
262
+ (dt.seconds * 1000)
263
+ (dt.microseconds // 1000))
265
def timeout_milliseconds(self):
266
"Return the 'timeout' attribute in milliseconds"
267
return self._datetime_to_milliseconds(self.timeout)
269
def interval_milliseconds(self):
270
"Return the 'interval' attribute in milliseconds"
271
return self._datetime_to_milliseconds(self.interval)
273
def __init__(self, name = None, disable_hook=None, config=None):
274
"""Note: the 'checker' key in 'config' sets the
275
'checker_command' attribute and *not* the 'checker'
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)
280
logger.debug(u"Creating client %r", self.name)
531
281
# Uppercase and remove spaces from fingerprint for later
532
282
# 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
284
self.fingerprint = (config[u"fingerprint"].upper()
286
logger.debug(u" Fingerprint: %s", self.fingerprint)
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
290
with closing(open(os.path.expanduser
292
(config[u"secfile"])))) as secfile:
293
self.secret = secfile.read()
295
raise TypeError(u"No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
298
self.created = datetime.datetime.utcnow()
300
self.last_enabled = None
301
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
304
self.disable_hook = disable_hook
539
305
self.checker = None
540
306
self.checker_initiator_tag = None
541
307
self.disable_initiator_tag = None
542
308
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
543
310
self.current_checker_command = None
545
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)
561
# Send notice to process children that client state has changed
562
def send_changedstate(self):
563
with self.changedstate:
564
self.changedstate.notify_all()
311
self.last_connect = None
566
313
def enable(self):
567
314
"""Start this client's checker and timeout hooks"""
568
if getattr(self, "enabled", False):
315
if getattr(self, u"enabled", False):
569
316
# Already enabled
571
self.expires = datetime.datetime.utcnow() + self.timeout
573
318
self.last_enabled = datetime.datetime.utcnow()
575
self.send_changedstate()
577
def disable(self, quiet=True):
578
"""Disable this client."""
579
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:
584
gobject.source_remove(self.disable_initiator_tag)
585
self.disable_initiator_tag = None
587
if getattr(self, "checker_initiator_tag", None) is not None:
588
gobject.source_remove(self.checker_initiator_tag)
589
self.checker_initiator_tag = None
593
self.send_changedstate()
594
# Do not run this again if called by a gobject.timeout_add
600
def init_checker(self):
601
319
# Schedule a new checker to be started an 'interval' from now,
602
320
# and every interval from then on.
603
if self.checker_initiator_tag is not None:
604
gobject.source_remove(self.checker_initiator_tag)
605
321
self.checker_initiator_tag = (gobject.timeout_add
607
.total_seconds() * 1000),
322
(self.interval_milliseconds(),
608
323
self.start_checker))
324
# Also start a new checker *right now*.
609
326
# Schedule a disable() when 'timeout' has passed
610
if self.disable_initiator_tag is not None:
327
self.disable_initiator_tag = (gobject.timeout_add
328
(self.timeout_milliseconds(),
333
"""Disable this client."""
334
if not getattr(self, "enabled", False):
336
logger.info(u"Disabling client %s", self.name)
337
if getattr(self, u"disable_initiator_tag", False):
611
338
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*.
339
self.disable_initiator_tag = None
340
if getattr(self, u"checker_initiator_tag", False):
341
gobject.source_remove(self.checker_initiator_tag)
342
self.checker_initiator_tag = None
344
if self.disable_hook:
345
self.disable_hook(self)
347
# Do not run this again if called by a gobject.timeout_add
351
self.disable_hook = None
619
354
def checker_callback(self, pid, condition, command):
620
355
"""The checker has completed, so take appropriate actions."""
621
356
self.checker_callback_tag = None
622
357
self.checker = None
623
358
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",
359
exitstatus = os.WEXITSTATUS(condition)
361
logger.info(u"Checker for %(name)s succeeded",
628
363
self.checked_ok()
630
logger.info("Checker for %(name)s failed",
365
logger.info(u"Checker for %(name)s failed",
633
self.last_checker_status = -1
634
logger.warning("Checker for %(name)s crashed?",
368
logger.warning(u"Checker for %(name)s crashed?",
637
371
def checked_ok(self):
638
"""Assert that the client has been seen, alive and well."""
372
"""Bump up the timeout for this client.
374
This should only be called when the client has been seen,
639
377
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()
378
gobject.source_remove(self.disable_initiator_tag)
379
self.disable_initiator_tag = (gobject.timeout_add
380
(self.timeout_milliseconds(),
659
383
def start_checker(self):
660
384
"""Start a new checker subprocess if one is not running.
662
386
If a checker already exists, leave it running and do
664
388
# The reason for not killing a running checker is that if we
665
# did that, and if a checker (for some reason) started running
666
# slowly and taking more than 'interval' time, then the client
667
# would inevitably timeout, since no checker would get a
668
# chance to run to completion. If we instead leave running
389
# did that, then if a checker (for some reason) started
390
# running slowly and taking more than 'interval' time, the
391
# client would inevitably timeout, since no checker would get
392
# a chance to run to completion. If we instead leave running
669
393
# checkers alone, the checker would have to take more time
670
# than 'timeout' for the client to be disabled, which is as it
394
# than 'timeout' for the client to be declared invalid, which
395
# is as it should be.
673
397
# If a checker exists, make sure it is not a zombie
398
if self.checker is not None:
675
399
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
676
except AttributeError:
678
except OSError as error:
679
if error.errno != errno.ECHILD:
683
logger.warning("Checker was a zombie")
401
logger.warning(u"Checker was a zombie")
684
402
gobject.source_remove(self.checker_callback_tag)
685
403
self.checker_callback(pid, status,
686
404
self.current_checker_command)
687
405
# Start a new checker if needed
688
406
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
408
# In case checker_command has exactly one % operator
409
command = self.checker_command % self.host
411
# Escape attributes for the shell
412
escaped_attrs = dict((key,
413
re.escape(unicode(str(val),
417
vars(self).iteritems())
419
command = self.checker_command % escaped_attrs
420
except TypeError, error:
421
logger.error(u'Could not format string "%s":'
422
u' %s', self.checker_command, error)
423
return True # Try again later
700
424
self.current_checker_command = command
702
logger.info("Starting checker %r for %s",
426
logger.info(u"Starting checker %r for %s",
703
427
command, self.name)
704
428
# We don't need to redirect stdout and stderr, since
705
429
# in normal mode, that is already done by daemon(),
706
430
# and in debug mode we don't want to. (Stdin is
707
431
# 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
432
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.
434
shell=True, cwd=u"/")
435
self.checker_callback_tag = (gobject.child_watch_add
437
self.checker_callback,
439
# The checker may have completed before the gobject
440
# watch was added. Check for this.
731
441
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)
443
gobject.source_remove(self.checker_callback_tag)
444
self.checker_callback(pid, status, command)
445
except OSError, error:
446
logger.error(u"Failed to start subprocess: %s",
742
448
# Re-run this periodically if run by gobject.timeout_add
747
453
if self.checker_callback_tag:
748
454
gobject.source_remove(self.checker_callback_tag)
749
455
self.checker_callback_tag = None
750
if getattr(self, "checker", None) is None:
456
if getattr(self, u"checker", None) is None:
752
logger.debug("Stopping checker for %(name)s", vars(self))
458
logger.debug(u"Stopping checker for %(name)s", vars(self))
754
self.checker.terminate()
460
os.kill(self.checker.pid, signal.SIGTERM)
756
462
#if self.checker.poll() is None:
757
# self.checker.kill()
758
except OSError as error:
463
# os.kill(self.checker.pid, signal.SIGKILL)
464
except OSError, error:
759
465
if error.errno != errno.ESRCH: # No such process
761
467
self.checker = None
764
def dbus_service_property(dbus_interface, signature="v",
765
access="readwrite", byte_arrays=False):
766
"""Decorators for marking methods of a DBusObjectWithProperties to
767
become properties on the D-Bus.
769
The decorated method will be called with no arguments by "Get"
770
and with one argument by "Set".
772
The parameters, where they are supported, are the same as
773
dbus.service.method, except there is only "signature", since the
774
type from Get() and the type sent to Set() is the same.
776
# Encoding deeply encoded byte arrays is not supported yet by the
777
# "Set" method, so we fail early here:
778
if byte_arrays and signature != "ay":
779
raise ValueError("Byte arrays not supported for non-'ay'"
780
" signature {!r}".format(signature))
782
func._dbus_is_property = True
783
func._dbus_interface = dbus_interface
784
func._dbus_signature = signature
785
func._dbus_access = access
786
func._dbus_name = func.__name__
787
if func._dbus_name.endswith("_dbus_property"):
788
func._dbus_name = func._dbus_name[:-14]
789
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
794
def dbus_interface_annotations(dbus_interface):
795
"""Decorator for marking functions returning interface annotations
799
@dbus_interface_annotations("org.example.Interface")
800
def _foo(self): # Function name does not matter
801
return {"org.freedesktop.DBus.Deprecated": "true",
802
"org.freedesktop.DBus.Property.EmitsChangedSignal":
806
func._dbus_is_interface = True
807
func._dbus_interface = dbus_interface
808
func._dbus_name = dbus_interface
813
def dbus_annotations(annotations):
814
"""Decorator to annotate D-Bus methods, signals or properties
817
@dbus_service_property("org.example.Interface", signature="b",
819
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
820
"org.freedesktop.DBus.Property."
821
"EmitsChangedSignal": "false"})
822
def Property_dbus_property(self):
823
return dbus.Boolean(False)
826
func._dbus_annotations = annotations
831
class DBusPropertyException(dbus.exceptions.DBusException):
832
"""A base class for D-Bus property-related exceptions
834
def __unicode__(self):
835
return unicode(str(self))
838
class DBusPropertyAccessException(DBusPropertyException):
839
"""A property's access permissions disallows an operation.
844
class DBusPropertyNotFound(DBusPropertyException):
845
"""An attempt was made to access a non-existing property.
850
class DBusObjectWithProperties(dbus.service.Object):
851
"""A D-Bus object with properties.
853
Classes inheriting from this can use the dbus_service_property
854
decorator to expose methods as D-Bus properties. It exposes the
855
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),
868
def _get_all_dbus_things(self, thing):
869
"""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)))
879
def _get_dbus_property(self, interface_name, property_name):
880
"""Returns a bound method if one exists which is a D-Bus
881
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)
892
raise DBusPropertyNotFound(self.dbus_object_path + ":"
893
+ interface_name + "."
896
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
898
def Get(self, interface_name, property_name):
899
"""Standard D-Bus property Get() method, see D-Bus standard.
901
prop = self._get_dbus_property(interface_name, property_name)
902
if prop._dbus_access == "write":
903
raise DBusPropertyAccessException(property_name)
905
if not hasattr(value, "variant_level"):
907
return type(value)(value, variant_level=value.variant_level+1)
909
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
910
def Set(self, interface_name, property_name, value):
911
"""Standard D-Bus property Set() method, see D-Bus standard.
913
prop = self._get_dbus_property(interface_name, property_name)
914
if prop._dbus_access == "read":
915
raise DBusPropertyAccessException(property_name)
916
if prop._dbus_get_args_options["byte_arrays"]:
917
# The byte_arrays option is not supported yet on
918
# 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)
927
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
928
out_signature="a{sv}")
929
def GetAll(self, interface_name):
930
"""Standard D-Bus property GetAll() method, see D-Bus
933
Note: Will not include properties with access="write".
936
for name, prop in self._get_all_dbus_things("property"):
938
and interface_name != prop._dbus_interface):
939
# Interface non-empty but did not match
941
# Ignore write-only properties
942
if prop._dbus_access == "write":
945
if not hasattr(value, "variant_level"):
946
properties[name] = value
948
properties[name] = type(value)(value, variant_level=
949
value.variant_level+1)
950
return dbus.Dictionary(properties, signature="sv")
952
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
954
path_keyword='object_path',
955
connection_keyword='connection')
956
def Introspect(self, object_path, connection):
957
"""Overloading of standard D-Bus method.
959
Inserts property tags and interface annotation tags.
961
xmlstring = dbus.service.Object.Introspect(self, object_path,
964
document = xml.dom.minidom.parseString(xmlstring)
965
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)
971
for if_tag in document.getElementsByTagName("interface"):
973
for tag in (make_tag(document, name, prop)
975
in self._get_all_dbus_things("property")
976
if prop._dbus_interface
977
== if_tag.getAttribute("name")):
978
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
# Add the names to the return values for the
1011
# "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")
1027
except (AttributeError, xml.dom.DOMException,
1028
xml.parsers.expat.ExpatError) as error:
1029
logger.error("Failed to override Introspection method",
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
class ClientDBus(Client, DBusObjectWithProperties):
469
def still_valid(self):
470
"""Has the timeout not yet passed for this client?"""
471
if not getattr(self, u"enabled", False):
473
now = datetime.datetime.utcnow()
474
if self.last_checked_ok is None:
475
return now < (self.created + self.timeout)
477
return now < (self.last_checked_ok + self.timeout)
480
class ClientDBus(Client, dbus.service.Object):
1214
481
"""A Client class using D-Bus
1217
484
dbus_object_path: dbus.ObjectPath
1218
485
bus: dbus.SystemBus()
1221
runtime_expansions = (Client.runtime_expansions
1222
+ ("dbus_object_path",))
1224
487
# dbus.service.Object doesn't use super(), so we can't either.
1226
489
def __init__(self, bus = None, *args, **kwargs):
1228
491
Client.__init__(self, *args, **kwargs)
1229
492
# 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
494
self.dbus_object_path = (dbus.ObjectPath
1235
("/clients/" + client_object_name))
1236
DBusObjectWithProperties.__init__(self, self.bus,
1237
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
496
+ self.name.replace(u".", u"_")))
497
dbus.service.Object.__init__(self, self.bus,
498
self.dbus_object_path)
501
def _datetime_to_dbus(dt, variant_level=0):
502
"""Convert a UTC datetime.datetime() to a D-Bus type."""
503
return dbus.String(dt.isoformat(),
504
variant_level=variant_level)
507
oldstate = getattr(self, u"enabled", False)
508
r = Client.enable(self)
509
if oldstate != self.enabled:
511
self.PropertyChanged(dbus.String(u"enabled"),
512
dbus.Boolean(True, variant_level=1))
513
self.PropertyChanged(
514
dbus.String(u"last_enabled"),
515
self._datetime_to_dbus(self.last_enabled,
519
def disable(self, signal = True):
520
oldstate = getattr(self, u"enabled", False)
521
r = Client.disable(self)
522
if signal and oldstate != self.enabled:
524
self.PropertyChanged(dbus.String(u"enabled"),
525
dbus.Boolean(False, variant_level=1))
1309
528
def __del__(self, *args, **kwargs):
1311
530
self.remove_from_connection()
1312
531
except LookupError:
1314
if hasattr(DBusObjectWithProperties, "__del__"):
1315
DBusObjectWithProperties.__del__(self, *args, **kwargs)
533
if hasattr(dbus.service.Object, u"__del__"):
534
dbus.service.Object.__del__(self, *args, **kwargs)
1316
535
Client.__del__(self, *args, **kwargs)
1318
537
def checker_callback(self, pid, condition, command,
1319
538
*args, **kwargs):
1320
539
self.checker_callback_tag = None
1321
540
self.checker = None
542
self.PropertyChanged(dbus.String(u"checker_running"),
543
dbus.Boolean(False, variant_level=1))
1322
544
if os.WIFEXITED(condition):
1323
545
exitstatus = os.WEXITSTATUS(condition)
1324
546
# Emit D-Bus signal
1334
556
return Client.checker_callback(self, pid, condition, command,
1335
557
*args, **kwargs)
559
def checked_ok(self, *args, **kwargs):
560
r = Client.checked_ok(self, *args, **kwargs)
562
self.PropertyChanged(
563
dbus.String(u"last_checked_ok"),
564
(self._datetime_to_dbus(self.last_checked_ok,
1337
568
def start_checker(self, *args, **kwargs):
1338
old_checker_pid = getattr(self.checker, "pid", None)
569
old_checker = self.checker
570
if self.checker is not None:
571
old_checker_pid = self.checker.pid
573
old_checker_pid = None
1339
574
r = Client.start_checker(self, *args, **kwargs)
1340
575
# Only if new checker process was started
1341
576
if (self.checker is not None
1342
577
and old_checker_pid != self.checker.pid):
1343
578
# Emit D-Bus signal
1344
579
self.CheckerStarted(self.current_checker_command)
1347
def _reset_approved(self):
1348
self.approved = None
1351
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
self.send_changedstate()
1357
## D-Bus methods, signals & properties
1358
_interface = "se.recompile.Mandos.Client"
1362
@dbus_interface_annotations(_interface)
1364
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
580
self.PropertyChanged(
581
dbus.String(u"checker_running"),
582
dbus.Boolean(True, variant_level=1))
585
def stop_checker(self, *args, **kwargs):
586
old_checker = getattr(self, u"checker", None)
587
r = Client.stop_checker(self, *args, **kwargs)
588
if (old_checker is not None
589
and getattr(self, u"checker", None) is None):
590
self.PropertyChanged(dbus.String(u"checker_running"),
591
dbus.Boolean(False, variant_level=1))
594
## D-Bus methods & signals
595
_interface = u"se.bsnet.fukt.Mandos.Client"
598
@dbus.service.method(_interface)
600
return self.checked_ok()
1369
602
# CheckerCompleted - signal
1370
@dbus.service.signal(_interface, signature="nxs")
603
@dbus.service.signal(_interface, signature=u"nxs")
1371
604
def CheckerCompleted(self, exitcode, waitstatus, command):
1375
608
# CheckerStarted - signal
1376
@dbus.service.signal(_interface, signature="s")
609
@dbus.service.signal(_interface, signature=u"s")
1377
610
def CheckerStarted(self, command):
614
# GetAllProperties - method
615
@dbus.service.method(_interface, out_signature=u"a{sv}")
616
def GetAllProperties(self):
618
return dbus.Dictionary({
619
dbus.String(u"name"):
620
dbus.String(self.name, variant_level=1),
621
dbus.String(u"fingerprint"):
622
dbus.String(self.fingerprint, variant_level=1),
623
dbus.String(u"host"):
624
dbus.String(self.host, variant_level=1),
625
dbus.String(u"created"):
626
self._datetime_to_dbus(self.created,
628
dbus.String(u"last_enabled"):
629
(self._datetime_to_dbus(self.last_enabled,
631
if self.last_enabled is not None
632
else dbus.Boolean(False, variant_level=1)),
633
dbus.String(u"enabled"):
634
dbus.Boolean(self.enabled, variant_level=1),
635
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
638
if self.last_checked_ok is not None
639
else dbus.Boolean (False, variant_level=1)),
640
dbus.String(u"timeout"):
641
dbus.UInt64(self.timeout_milliseconds(),
643
dbus.String(u"interval"):
644
dbus.UInt64(self.interval_milliseconds(),
646
dbus.String(u"checker"):
647
dbus.String(self.checker_command,
649
dbus.String(u"checker_running"):
650
dbus.Boolean(self.checker is not None,
652
dbus.String(u"object_path"):
653
dbus.ObjectPath(self.dbus_object_path,
657
# IsStillValid - method
658
@dbus.service.method(_interface, out_signature=u"b")
659
def IsStillValid(self):
660
return self.still_valid()
1381
662
# PropertyChanged - signal
1382
@dbus.service.signal(_interface, signature="sv")
663
@dbus.service.signal(_interface, signature=u"sv")
1383
664
def PropertyChanged(self, property, value):
1387
# GotSecret - signal
668
# ReceivedSecret - signal
1388
669
@dbus.service.signal(_interface)
1389
def GotSecret(self):
1391
Is sent after a successful transfer of secret from the Mandos
1392
server to mandos-client
670
def ReceivedSecret(self):
1396
674
# Rejected - signal
1397
@dbus.service.signal(_interface, signature="s")
1398
def Rejected(self, reason):
675
@dbus.service.signal(_interface)
1402
# NeedApproval - signal
1403
@dbus.service.signal(_interface, signature="tb")
1404
def NeedApproval(self, timeout, default):
1406
return self.need_approval()
1411
@dbus.service.method(_interface, in_signature="b")
1412
def Approve(self, value):
1415
# CheckedOK - method
1416
@dbus.service.method(_interface)
1417
def CheckedOK(self):
680
# SetChecker - method
681
@dbus.service.method(_interface, in_signature=u"s")
682
def SetChecker(self, checker):
683
"D-Bus setter method"
684
self.checker_command = checker
686
self.PropertyChanged(dbus.String(u"checker"),
687
dbus.String(self.checker_command,
691
@dbus.service.method(_interface, in_signature=u"s")
692
def SetHost(self, host):
693
"D-Bus setter method"
696
self.PropertyChanged(dbus.String(u"host"),
697
dbus.String(self.host, variant_level=1))
699
# SetInterval - method
700
@dbus.service.method(_interface, in_signature=u"t")
701
def SetInterval(self, milliseconds):
702
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
704
self.PropertyChanged(dbus.String(u"interval"),
705
(dbus.UInt64(self.interval_milliseconds(),
709
@dbus.service.method(_interface, in_signature=u"ay",
711
def SetSecret(self, secret):
712
"D-Bus setter method"
713
self.secret = str(secret)
715
# SetTimeout - method
716
@dbus.service.method(_interface, in_signature=u"t")
717
def SetTimeout(self, milliseconds):
718
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
720
self.PropertyChanged(dbus.String(u"timeout"),
721
(dbus.UInt64(self.timeout_milliseconds(),
1420
724
# Enable - method
1421
725
@dbus.service.method(_interface)
1440
744
def StopChecker(self):
1441
745
self.stop_checker()
1445
# ApprovalPending - property
1446
@dbus_service_property(_interface, signature="b", access="read")
1447
def ApprovalPending_dbus_property(self):
1448
return dbus.Boolean(bool(self.approvals_pending))
1450
# ApprovedByDefault - property
1451
@dbus_service_property(_interface, signature="b",
1453
def ApprovedByDefault_dbus_property(self, value=None):
1454
if value is None: # get
1455
return dbus.Boolean(self.approved_by_default)
1456
self.approved_by_default = bool(value)
1458
# ApprovalDelay - property
1459
@dbus_service_property(_interface, signature="t",
1461
def ApprovalDelay_dbus_property(self, value=None):
1462
if value is None: # get
1463
return dbus.UInt64(self.approval_delay.total_seconds()
1465
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1467
# ApprovalDuration - property
1468
@dbus_service_property(_interface, signature="t",
1470
def ApprovalDuration_dbus_property(self, value=None):
1471
if value is None: # get
1472
return dbus.UInt64(self.approval_duration.total_seconds()
1474
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1477
@dbus_service_property(_interface, signature="s", access="read")
1478
def Name_dbus_property(self):
1479
return dbus.String(self.name)
1481
# Fingerprint - property
1482
@dbus_service_property(_interface, signature="s", access="read")
1483
def Fingerprint_dbus_property(self):
1484
return dbus.String(self.fingerprint)
1487
@dbus_service_property(_interface, signature="s",
1489
def Host_dbus_property(self, value=None):
1490
if value is None: # get
1491
return dbus.String(self.host)
1492
self.host = unicode(value)
1494
# Created - property
1495
@dbus_service_property(_interface, signature="s", access="read")
1496
def Created_dbus_property(self):
1497
return datetime_to_dbus(self.created)
1499
# LastEnabled - property
1500
@dbus_service_property(_interface, signature="s", access="read")
1501
def LastEnabled_dbus_property(self):
1502
return datetime_to_dbus(self.last_enabled)
1504
# Enabled - property
1505
@dbus_service_property(_interface, signature="b",
1507
def Enabled_dbus_property(self, value=None):
1508
if value is None: # get
1509
return dbus.Boolean(self.enabled)
1515
# LastCheckedOK - property
1516
@dbus_service_property(_interface, signature="s",
1518
def LastCheckedOK_dbus_property(self, value=None):
1519
if value is not None:
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)
1540
# Timeout - property
1541
@dbus_service_property(_interface, signature="t",
1543
def Timeout_dbus_property(self, value=None):
1544
if value is None: # get
1545
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1546
old_timeout = self.timeout
1547
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)
1574
# Interval - property
1575
@dbus_service_property(_interface, signature="t",
1577
def Interval_dbus_property(self, value=None):
1578
if value is None: # get
1579
return dbus.UInt64(self.interval.total_seconds() * 1000)
1580
self.interval = datetime.timedelta(0, 0, 0, value)
1581
if getattr(self, "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
1590
# Checker - property
1591
@dbus_service_property(_interface, signature="s",
1593
def Checker_dbus_property(self, value=None):
1594
if value is None: # get
1595
return dbus.String(self.checker_command)
1596
self.checker_command = unicode(value)
1598
# CheckerRunning - property
1599
@dbus_service_property(_interface, signature="b",
1601
def CheckerRunning_dbus_property(self, value=None):
1602
if value is None: # get
1603
return dbus.Boolean(self.checker is not None)
1605
self.start_checker()
1609
# ObjectPath - property
1610
@dbus_service_property(_interface, signature="o", access="read")
1611
def ObjectPath_dbus_property(self):
1612
return self.dbus_object_path # is already a dbus.ObjectPath
1615
@dbus_service_property(_interface, signature="ay",
1616
access="write", byte_arrays=True)
1617
def Secret_dbus_property(self, value):
1618
self.secret = str(value)
1623
class ProxyClient(object):
1624
def __init__(self, child_pipe, fpr, address):
1625
self._pipe = child_pipe
1626
self._pipe.send(('init', fpr, address))
1627
if not self._pipe.recv():
1630
def __getattribute__(self, name):
1632
return super(ProxyClient, self).__getattribute__(name)
1633
self._pipe.send(('getattr', name))
1634
data = self._pipe.recv()
1635
if data[0] == 'data':
1637
if data[0] == 'function':
1638
def func(*args, **kwargs):
1639
self._pipe.send(('funcall', name, args, kwargs))
1640
return self._pipe.recv()[1]
1643
def __setattr__(self, name, value):
1645
return super(ProxyClient, self).__setattr__(name, value)
1646
self._pipe.send(('setattr', name, value))
1649
750
class ClientHandler(socketserver.BaseRequestHandler, object):
1650
751
"""A class to handle client connections.
1653
754
Note: This will run in its own forked process."""
1655
756
def handle(self):
1656
with contextlib.closing(self.server.child_pipe) as child_pipe:
1657
logger.info("TCP connection from: %s",
1658
unicode(self.client_address))
1659
logger.debug("Pipe FD: %d",
1660
self.server.child_pipe.fileno())
757
logger.info(u"TCP connection from: %s",
758
unicode(self.client_address))
759
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
760
# Open IPC pipe to parent process
761
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1662
762
session = (gnutls.connection
1663
763
.ClientSession(self.request,
1664
764
gnutls.connection
1665
765
.X509Credentials()))
767
line = self.request.makefile().readline()
768
logger.debug(u"Protocol version: %r", line)
770
if int(line.strip().split()[0]) > 1:
772
except (ValueError, IndexError, RuntimeError), error:
773
logger.error(u"Unknown protocol version: %s", error)
1667
776
# Note: gnutls.connection.X509Credentials is really a
1668
777
# generic GnuTLS certificate credentials object so long as
1669
778
# no X.509 keys are added to it. Therefore, we can use it
1670
779
# here despite using OpenPGP certificates.
1672
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1673
# "+AES-256-CBC", "+SHA1",
1674
# "+COMP-NULL", "+CTYPE-OPENPGP",
781
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
782
# u"+AES-256-CBC", u"+SHA1",
783
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1676
785
# Use a fallback default, since this MUST be set.
1677
786
priority = self.server.gnutls_priority
1678
787
if priority is None:
1680
789
(gnutls.library.functions
1681
790
.gnutls_priority_set_direct(session._c_object,
1682
791
priority, None))
1684
# Start communication using the Mandos protocol
1685
# Get protocol number
1686
line = self.request.makefile().readline()
1687
logger.debug("Protocol version: %r", line)
1689
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)
1695
# Start GnuTLS connection
1697
794
session.handshake()
1698
except gnutls.errors.GNUTLSError as error:
1699
logger.warning("Handshake failed: %s", error)
795
except gnutls.errors.GNUTLSError, error:
796
logger.warning(u"Handshake failed: %s", error)
1700
797
# Do not run session.bye() here: the session is not
1701
798
# established. Just abandon the request.
1703
logger.debug("Handshake succeeded")
1705
approval_required = False
800
logger.debug(u"Handshake succeeded")
1708
fpr = self.fingerprint(self.peer_certificate
1711
gnutls.errors.GNUTLSError) as error:
1712
logger.warning("Bad certificate: %s", error)
1714
logger.debug("Fingerprint: %s", fpr)
1717
client = ProxyClient(child_pipe, fpr,
1718
self.client_address)
1722
if client.approval_delay:
1723
delay = client.approval_delay
1724
client.approvals_pending += 1
1725
approval_required = True
1728
if not client.enabled:
1729
logger.info("Client %s is disabled",
1731
if self.server.use_dbus:
1733
client.Rejected("Disabled")
1736
if client.approved or not client.approval_delay:
1737
#We are approved or approval is disabled
1739
elif client.approved is None:
1740
logger.info("Client %s needs approval",
1742
if self.server.use_dbus:
1744
client.NeedApproval(
1745
client.approval_delay.total_seconds()
1746
* 1000, client.approved_by_default)
1748
logger.warning("Client %s was not approved",
1750
if self.server.use_dbus:
1752
client.Rejected("Denied")
1755
#wait until timeout or approved
1756
time = datetime.datetime.now()
1757
client.changedstate.acquire()
1758
client.changedstate.wait(delay.total_seconds())
1759
client.changedstate.release()
1760
time2 = datetime.datetime.now()
1761
if (time2 - time) >= delay:
1762
if not client.approved_by_default:
1763
logger.warning("Client %s timed out while"
1764
" waiting for approval",
1766
if self.server.use_dbus:
1768
client.Rejected("Approval timed out")
1773
delay -= time2 - time
1776
while sent_size < len(client.secret):
1778
sent = session.send(client.secret[sent_size:])
1779
except gnutls.errors.GNUTLSError as error:
1780
logger.warning("gnutls send failed",
1783
logger.debug("Sent: %d, remaining: %d",
1784
sent, len(client.secret)
1785
- (sent_size + sent))
1788
logger.info("Sending secret to %s", client.name)
1789
# bump the timeout using extended_timeout
1790
client.bump_timeout(client.extended_timeout)
1791
if self.server.use_dbus:
802
fpr = self.fingerprint(self.peer_certificate(session))
803
except (TypeError, gnutls.errors.GNUTLSError), error:
804
logger.warning(u"Bad certificate: %s", error)
807
logger.debug(u"Fingerprint: %s", fpr)
1796
if approval_required:
1797
client.approvals_pending -= 1
1800
except gnutls.errors.GNUTLSError as error:
1801
logger.warning("GnuTLS bye failed",
809
for c in self.server.clients:
810
if c.fingerprint == fpr:
814
ipc.write(u"NOTFOUND %s\n" % fpr)
817
# Have to check if client.still_valid(), since it is
818
# possible that the client timed out while establishing
819
# the GnuTLS session.
820
if not client.still_valid():
821
ipc.write(u"INVALID %s\n" % client.name)
824
ipc.write(u"SENDING %s\n" % client.name)
826
while sent_size < len(client.secret):
827
sent = session.send(client.secret[sent_size:])
828
logger.debug(u"Sent: %d, remaining: %d",
829
sent, len(client.secret)
830
- (sent_size + sent))
1805
835
def peer_certificate(session):
1911
922
use_ipv6: Boolean; to use IPv6 or not
1913
924
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().
925
interface=None, use_ipv6=True):
1918
926
self.interface = interface
1920
928
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
929
socketserver.TCPServer.__init__(self, server_address,
1948
930
RequestHandlerClass)
1950
931
def server_bind(self):
1951
932
"""This overrides the normal server_bind() function
1952
933
to bind to an interface if one was specified, and also NOT to
1953
934
bind to an address or port if they were not specified."""
1954
935
if self.interface is not None:
1955
936
if SO_BINDTODEVICE is None:
1956
logger.error("SO_BINDTODEVICE does not exist;"
1957
" cannot bind to interface %s",
937
logger.error(u"SO_BINDTODEVICE does not exist;"
938
u" cannot bind to interface %s",
1961
942
self.socket.setsockopt(socket.SOL_SOCKET,
1962
943
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)
946
except socket.error, error:
947
if error[0] == errno.EPERM:
948
logger.error(u"No permission to"
949
u" bind to interface %s",
951
elif error[0] == errno.ENOPROTOOPT:
952
logger.error(u"SO_BINDTODEVICE not available;"
953
u" cannot bind to interface %s",
1977
957
# Only bind(2) the socket if we really need to.
1978
958
if self.server_address[0] or self.server_address[1]:
1979
959
if not self.server_address[0]:
1980
960
if self.address_family == socket.AF_INET6:
1981
any_address = "::" # in6addr_any
961
any_address = u"::" # in6addr_any
1983
any_address = "0.0.0.0" # INADDR_ANY
963
any_address = socket.INADDR_ANY
1984
964
self.server_address = (any_address,
1985
965
self.server_address[1])
1986
966
elif not self.server_address[1]:
2002
982
clients: set of Client objects
2003
983
gnutls_priority GnuTLS priority string
2004
984
use_dbus: Boolean; to emit D-Bus signals or not
985
clients: set of Client objects
986
gnutls_priority GnuTLS priority string
987
use_dbus: Boolean; to emit D-Bus signals or not
2006
989
Assumes a gobject.MainLoop event loop.
2008
991
def __init__(self, server_address, RequestHandlerClass,
2009
992
interface=None, use_ipv6=True, clients=None,
2010
gnutls_priority=None, use_dbus=True, socketfd=None):
993
gnutls_priority=None, use_dbus=True):
2011
994
self.enabled = False
2012
995
self.clients = clients
2013
996
if self.clients is None:
2015
998
self.use_dbus = use_dbus
2016
999
self.gnutls_priority = gnutls_priority
2017
1000
IPv6_TCPServer.__init__(self, server_address,
2018
1001
RequestHandlerClass,
2019
1002
interface = interface,
2020
use_ipv6 = use_ipv6,
2021
socketfd = socketfd)
1003
use_ipv6 = use_ipv6)
2022
1004
def server_activate(self):
2023
1005
if self.enabled:
2024
1006
return socketserver.TCPServer.server_activate(self)
2026
1007
def enable(self):
2027
1008
self.enabled = True
2029
def add_pipe(self, parent_pipe, proc):
1009
def add_pipe(self, pipe):
2030
1010
# Call "handle_ipc" for both data and EOF events
2031
gobject.io_add_watch(parent_pipe.fileno(),
2032
gobject.IO_IN | gobject.IO_HUP,
2033
functools.partial(self.handle_ipc,
2038
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
2046
# Read a request from the child
2047
request = parent_pipe.recv()
2048
command = request[0]
2050
if command == 'init':
2052
address = request[2]
2054
for c in self.clients.itervalues():
2055
if c.fingerprint == fpr:
2059
logger.info("Client not found for fingerprint: %s, ad"
2060
"dress: %s", fpr, address)
2063
mandos_dbus_service.ClientNotFound(fpr,
2065
parent_pipe.send(False)
2068
gobject.io_add_watch(parent_pipe.fileno(),
2069
gobject.IO_IN | gobject.IO_HUP,
2070
functools.partial(self.handle_ipc,
2076
parent_pipe.send(True)
2077
# remove the old hook in favor of the new above hook on
2080
if command == 'funcall':
2081
funcname = request[1]
2085
parent_pipe.send(('data', getattr(client_object,
2089
if command == 'getattr':
2090
attrname = request[1]
2091
if callable(client_object.__getattribute__(attrname)):
2092
parent_pipe.send(('function',))
2094
parent_pipe.send(('data', client_object
2095
.__getattribute__(attrname)))
2097
if command == 'setattr':
2098
attrname = request[1]
2100
setattr(client_object, attrname, value)
1011
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1013
def handle_ipc(self, source, condition, file_objects={}):
1015
gobject.IO_IN: u"IN", # There is data to read.
1016
gobject.IO_OUT: u"OUT", # Data can be written (without
1018
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1019
gobject.IO_ERR: u"ERR", # Error condition.
1020
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1021
# broken, usually for pipes and
1024
conditions_string = ' | '.join(name
1026
condition_names.iteritems()
1027
if cond & condition)
1028
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1031
# Turn the pipe file descriptor into a Python file object
1032
if source not in file_objects:
1033
file_objects[source] = os.fdopen(source, u"r", 1)
1035
# Read a line from the file object
1036
cmdline = file_objects[source].readline()
1037
if not cmdline: # Empty line means end of file
1038
# close the IPC pipe
1039
file_objects[source].close()
1040
del file_objects[source]
1042
# Stop calling this function
1045
logger.debug(u"IPC command: %r", cmdline)
1047
# Parse and act on command
1048
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1050
if cmd == u"NOTFOUND":
1051
logger.warning(u"Client not found for fingerprint: %s",
1055
mandos_dbus_service.ClientNotFound(args)
1056
elif cmd == u"INVALID":
1057
for client in self.clients:
1058
if client.name == args:
1059
logger.warning(u"Client %s is invalid", args)
1065
logger.error(u"Unknown client %s is invalid", args)
1066
elif cmd == u"SENDING":
1067
for client in self.clients:
1068
if client.name == args:
1069
logger.info(u"Sending secret to %s", client.name)
1073
client.ReceivedSecret()
1076
logger.error(u"Sending secret to unknown client %s",
1079
logger.error(u"Unknown IPC command: %r", cmdline)
1081
# Keep calling this function
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
1085
def string_to_delta(interval):
2205
1086
"""Parse a string and return a datetime.timedelta
2207
>>> string_to_delta('7d')
1088
>>> string_to_delta(u'7d')
2208
1089
datetime.timedelta(7)
2209
>>> string_to_delta('60s')
1090
>>> string_to_delta(u'60s')
2210
1091
datetime.timedelta(0, 60)
2211
>>> string_to_delta('60m')
1092
>>> string_to_delta(u'60m')
2212
1093
datetime.timedelta(0, 3600)
2213
>>> string_to_delta('24h')
1094
>>> string_to_delta(u'24h')
2214
1095
datetime.timedelta(1)
2215
>>> string_to_delta('1w')
1096
>>> string_to_delta(u'1w')
2216
1097
datetime.timedelta(7)
2217
>>> string_to_delta('5m 30s')
1098
>>> string_to_delta(u'5m 30s')
2218
1099
datetime.timedelta(0, 330)
2222
return rfc3339_duration_to_delta(interval)
2226
1101
timevalue = datetime.timedelta(0)
2227
1102
for s in interval.split():
2229
1104
suffix = unicode(s[-1])
2230
1105
value = int(s[:-1])
2232
1107
delta = datetime.timedelta(value)
1108
elif suffix == u"s":
2234
1109
delta = datetime.timedelta(0, value)
1110
elif suffix == u"m":
2236
1111
delta = datetime.timedelta(0, 0, 0, 0, value)
1112
elif suffix == u"h":
2238
1113
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1114
elif suffix == u"w":
2240
1115
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))
1118
except (ValueError, IndexError):
2246
1120
timevalue += delta
2247
1121
return timevalue
1124
def if_nametoindex(interface):
1125
"""Call the C function if_nametoindex(), or equivalent
1127
Note: This function cannot accept a unicode string."""
1128
global if_nametoindex
1130
if_nametoindex = (ctypes.cdll.LoadLibrary
1131
(ctypes.util.find_library(u"c"))
1133
except (OSError, AttributeError):
1134
logger.warning(u"Doing if_nametoindex the hard way")
1135
def if_nametoindex(interface):
1136
"Get an interface index the hard way, i.e. using fcntl()"
1137
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1138
with closing(socket.socket()) as s:
1139
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1140
struct.pack(str(u"16s16x"),
1142
interface_index = struct.unpack(str(u"I"),
1144
return interface_index
1145
return if_nametoindex(interface)
2250
1148
def daemon(nochdir = False, noclose = False):
2251
1149
"""See daemon(3). Standard BSD Unix function.
2276
##################################################################
1174
######################################################################
2277
1175
# 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()
1177
parser = optparse.OptionParser(version = "%%prog %s" % version)
1178
parser.add_option("-i", u"--interface", type=u"string",
1179
metavar="IF", help=u"Bind to interface IF")
1180
parser.add_option("-a", u"--address", type=u"string",
1181
help=u"Address to listen for requests on")
1182
parser.add_option("-p", u"--port", type=u"int",
1183
help=u"Port number to receive requests on")
1184
parser.add_option("--check", action=u"store_true",
1185
help=u"Run self-test")
1186
parser.add_option("--debug", action=u"store_true",
1187
help=u"Debug mode; run in foreground and log to"
1189
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1190
u" priority string (see GnuTLS documentation)")
1191
parser.add_option("--servicename", type=u"string",
1192
metavar=u"NAME", help=u"Zeroconf service name")
1193
parser.add_option("--configdir", type=u"string",
1194
default=u"/etc/mandos", metavar=u"DIR",
1195
help=u"Directory to search for configuration"
1197
parser.add_option("--no-dbus", action=u"store_false",
1198
dest=u"use_dbus", help=u"Do not provide D-Bus"
1199
u" system bus interface")
1200
parser.add_option("--no-ipv6", action=u"store_false",
1201
dest=u"use_ipv6", help=u"Do not use IPv6")
1202
options = parser.parse_args()[0]
2326
1204
if options.check:
2328
fail_count, test_count = doctest.testmod()
2329
sys.exit(os.EX_OK if fail_count == 0 else 1)
2331
1209
# 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",
1210
server_defaults = { u"interface": u"",
1215
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1216
u"servicename": u"Mandos",
1217
u"use_dbus": u"True",
1218
u"use_ipv6": u"True",
2349
1221
# Parse config file for server-global settings
2350
1222
server_config = configparser.SafeConfigParser(server_defaults)
2351
1223
del server_defaults
2352
1224
server_config.read(os.path.join(options.configdir,
2354
1226
# Convert the SafeConfigParser object to a dict
2355
1227
server_settings = server_config.defaults()
2356
1228
# 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",
1229
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1230
server_settings[option] = server_config.getboolean(u"DEFAULT",
2360
1232
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
1233
server_settings["port"] = server_config.getint(u"DEFAULT",
2372
1235
del server_config
2374
1237
# Override the settings from the config file with command line
2375
1238
# 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"):
1239
for option in (u"interface", u"address", u"port", u"debug",
1240
u"priority", u"servicename", u"configdir",
1241
u"use_dbus", u"use_ipv6"):
2380
1242
value = getattr(options, option)
2381
1243
if value is not None:
2382
1244
server_settings[option] = value
2385
1247
for option in server_settings.keys():
2386
1248
if type(server_settings[option]) is str:
2387
1249
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
1250
# Now we have our good server settings in "server_settings"
2397
1252
##################################################################
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
1254
# 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":
1255
debug = server_settings[u"debug"]
1256
use_dbus = server_settings[u"use_dbus"]
1257
use_ipv6 = server_settings[u"use_ipv6"]
1260
syslogger.setLevel(logging.WARNING)
1261
console.setLevel(logging.WARNING)
1263
if server_settings[u"servicename"] != u"Mandos":
2425
1264
syslogger.setFormatter(logging.Formatter
2426
('Mandos ({}) [%(process)d]:'
2427
' %(levelname)s: %(message)s'
2428
.format(server_settings
1265
(u'Mandos (%s) [%%(process)d]:'
1266
u' %%(levelname)s: %%(message)s'
1267
% server_settings[u"servicename"]))
2431
1269
# Parse config file with clients
2432
client_config = configparser.SafeConfigParser(Client
2434
client_config.read(os.path.join(server_settings["configdir"],
1270
client_defaults = { u"timeout": u"1h",
1272
u"checker": u"fping -q -- %%(host)s",
1275
client_config = configparser.SafeConfigParser(client_defaults)
1276
client_config.read(os.path.join(server_settings[u"configdir"],
2437
1279
global mandos_dbus_service
2438
1280
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"]),
1282
tcp_server = MandosServer((server_settings[u"address"],
1283
server_settings[u"port"]),
2446
interface=(server_settings["interface"]
1285
interface=server_settings[u"interface"],
2448
1286
use_ipv6=use_ipv6,
2449
1287
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,
1288
server_settings[u"priority"],
1290
pidfilename = u"/var/run/mandos.pid"
1292
pidfile = open(pidfilename, u"w")
1294
logger.error(u"Could not open file %r", pidfilename)
2464
for name in ("_mandos", "mandos", "nobody"):
1297
uid = pwd.getpwnam(u"_mandos").pw_uid
1298
gid = pwd.getpwnam(u"_mandos").pw_gid
2466
uid = pwd.getpwnam(name).pw_uid
2467
gid = pwd.getpwnam(name).pw_gid
1301
uid = pwd.getpwnam(u"mandos").pw_uid
1302
gid = pwd.getpwnam(u"mandos").pw_gid
2469
1303
except KeyError:
1305
uid = pwd.getpwnam(u"nobody").pw_uid
1306
gid = pwd.getpwnam(u"nobody").pw_gid
2477
except OSError as error:
2478
if error.errno != errno.EPERM:
1313
except OSError, error:
1314
if error[0] != errno.EPERM:
1317
# Enable all possible GnuTLS debugging
2482
# Enable all possible GnuTLS debugging
2484
1319
# "Use a log level over 10 to enable all debugging options."
2485
1320
# - GnuTLS manual
2486
1321
gnutls.library.functions.gnutls_global_set_log_level(11)
2488
1323
@gnutls.library.types.gnutls_log_func
2489
1324
def debug_gnutls(level, string):
2490
logger.debug("GnuTLS: %s", string[:-1])
1325
logger.debug(u"GnuTLS: %s", string[:-1])
2492
1327
(gnutls.library.functions
2493
1328
.gnutls_global_set_log_function(debug_gnutls))
2495
# Redirect stdin so all checkers get /dev/null
2496
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2497
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()
2510
1330
global main_loop
2511
1331
# From the Avahi example code
2512
DBusGMainLoop(set_as_default=True)
1332
DBusGMainLoop(set_as_default=True )
2513
1333
main_loop = gobject.MainLoop()
2514
1334
bus = dbus.SystemBus()
2515
1335
# End of Avahi example code
2518
bus_name = dbus.service.BusName("se.recompile.Mandos",
2519
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)
2526
server_settings["use_dbus"] = False
2527
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"])))
2538
global multiprocessing_manager
2539
multiprocessing_manager = multiprocessing.Manager()
1337
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1338
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1339
service = AvahiService(name = server_settings[u"servicename"],
1340
servicetype = u"_mandos._tcp",
1341
protocol = protocol, bus = bus)
1342
if server_settings["interface"]:
1343
service.interface = (if_nametoindex
1344
(str(server_settings[u"interface"])))
2541
1346
client_class = Client
2543
1348
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))
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)
1349
tcp_server.clients.update(set(
1350
client_class(name = section,
1351
config= dict(client_config.items(section)))
1352
for section in client_config.sections()))
2652
1353
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",
1354
logger.warning(u"No clients defined")
1357
# Redirect stdin so all checkers get /dev/null
1358
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1359
os.dup2(null, sys.stdin.fileno())
1363
# No console logging
1364
logger.removeHandler(console)
1365
# Close all input and output, do double fork, etc.
1369
with closing(pidfile):
1371
pidfile.write(str(pid) + "\n")
1374
logger.error(u"Could not write to file %r with PID %d",
1377
# "pidfile" was never created
1382
"Cleanup function; run on exit"
1385
while tcp_server.clients:
1386
client = tcp_server.clients.pop()
1387
client.disable_hook = None
1390
atexit.register(cleanup)
1393
signal.signal(signal.SIGINT, signal.SIG_IGN)
2667
1394
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2668
1395
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):
1398
class MandosDBusService(dbus.service.Object):
2674
1399
"""A D-Bus proxy object"""
2675
1400
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")
2686
def ClientAdded(self, objpath):
2690
@dbus.service.signal(_interface, signature="ss")
2691
def ClientNotFound(self, fingerprint, address):
2695
@dbus.service.signal(_interface, signature="os")
1401
dbus.service.Object.__init__(self, bus, u"/")
1402
_interface = u"se.bsnet.fukt.Mandos"
1404
@dbus.service.signal(_interface, signature=u"oa{sv}")
1405
def ClientAdded(self, objpath, properties):
1409
@dbus.service.signal(_interface, signature=u"s")
1410
def ClientNotFound(self, fingerprint):
1414
@dbus.service.signal(_interface, signature=u"os")
2696
1415
def ClientRemoved(self, objpath, name):
2700
@dbus.service.method(_interface, out_signature="ao")
1419
@dbus.service.method(_interface, out_signature=u"ao")
2701
1420
def GetAllClients(self):
2703
1422
return dbus.Array(c.dbus_object_path
2705
tcp_server.clients.itervalues())
1423
for c in tcp_server.clients)
2707
1425
@dbus.service.method(_interface,
2708
out_signature="a{oa{sv}}")
1426
out_signature=u"a{oa{sv}}")
2709
1427
def GetAllClientsWithProperties(self):
2711
1429
return dbus.Dictionary(
2712
((c.dbus_object_path, c.GetAll(""))
2713
for c in tcp_server.clients.itervalues()),
1430
((c.dbus_object_path, c.GetAllProperties())
1431
for c in tcp_server.clients),
1432
signature=u"oa{sv}")
2716
@dbus.service.method(_interface, in_signature="o")
1434
@dbus.service.method(_interface, in_signature=u"o")
2717
1435
def RemoveClient(self, object_path):
2719
for c in tcp_server.clients.itervalues():
1437
for c in tcp_server.clients:
2720
1438
if c.dbus_object_path == object_path:
2721
del tcp_server.clients[c.name]
1439
tcp_server.clients.remove(c)
2722
1440
c.remove_from_connection()
2723
1441
# Don't signal anything except ClientRemoved
2724
c.disable(quiet=True)
1442
c.disable(signal=False)
2725
1443
# Emit D-Bus signal
2726
1444
self.ClientRemoved(object_path, c.name)
2728
raise KeyError(object_path)
2732
1450
mandos_dbus_service = MandosDBusService()
2735
"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
while tcp_server.clients:
2796
name, client = tcp_server.clients.popitem()
2798
client.remove_from_connection()
2799
# Don't signal anything except ClientRemoved
2800
client.disable(quiet=True)
2803
mandos_dbus_service.ClientRemoved(client
2806
client_settings.clear()
2808
atexit.register(cleanup)
2810
for client in tcp_server.clients.itervalues():
1452
for client in tcp_server.clients:
2812
1454
# Emit D-Bus signal
2813
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2814
# Need to initiate checking of clients
2816
client.init_checker()
1455
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1456
client.GetAllProperties())
2818
1459
tcp_server.enable()
2819
1460
tcp_server.server_activate()
2821
1462
# Find out what port we got
2823
service.port = tcp_server.socket.getsockname()[1]
1463
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())
1465
logger.info(u"Now listening on address %r, port %d,"
1466
" flowinfo %d, scope_id %d"
1467
% tcp_server.socket.getsockname())
2829
logger.info("Now listening on address %r, port %d",
2830
*tcp_server.socket.getsockname())
1469
logger.info(u"Now listening on address %r, port %d"
1470
% tcp_server.socket.getsockname())
2832
1472
#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
1475
# From the Avahi example code
1478
except dbus.exceptions.DBusException, error:
1479
logger.critical(u"DBusException: %s", error)
1481
# End of Avahi example code
2845
1483
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2846
1484
lambda *args, **kwargs:
2847
1485
(tcp_server.handle_request
2848
1486
(*args[2:], **kwargs) or True))
2850
logger.debug("Starting main loop")
1488
logger.debug(u"Starting main loop")
2851
1489
main_loop.run()
2852
except AvahiError as error:
2853
logger.critical("Avahi Error", exc_info=error)
1490
except AvahiError, error:
1491
logger.critical(u"AvahiError: %s", error)
2856
1493
except KeyboardInterrupt:
2858
print("", file=sys.stderr)
2859
logger.debug("Server received KeyboardInterrupt")
2860
logger.debug("Server exiting")
2861
# Must run before the D-Bus bus name gets deregistered
1496
logger.debug(u"Server received KeyboardInterrupt")
1497
logger.debug(u"Server exiting")
2864
1499
if __name__ == '__main__':