79
100
except ImportError:
80
101
SO_BINDTODEVICE = None
85
logger = logging.Logger(u'mandos')
86
syslogger = (logging.handlers.SysLogHandler
87
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
88
address = "/dev/log"))
89
syslogger.setFormatter(logging.Formatter
90
(u'Mandos [%(process)d]: %(levelname)s:'
92
logger.addHandler(syslogger)
94
console = logging.StreamHandler()
95
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
98
logger.addHandler(console)
103
if sys.version_info.major == 2:
107
stored_state_file = "clients.pickle"
109
logger = logging.getLogger()
113
if_nametoindex = ctypes.cdll.LoadLibrary(
114
ctypes.util.find_library("c")).if_nametoindex
115
except (OSError, AttributeError):
117
def if_nametoindex(interface):
118
"Get an interface index the hard way, i.e. using fcntl()"
119
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
120
with contextlib.closing(socket.socket()) as s:
121
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
122
struct.pack(b"16s16x", interface))
123
interface_index = struct.unpack("I", ifreq[16:20])[0]
124
return interface_index
127
def initlogger(debug, level=logging.WARNING):
128
"""init logger and add loglevel"""
131
syslogger = (logging.handlers.SysLogHandler(
132
facility = logging.handlers.SysLogHandler.LOG_DAEMON,
133
address = "/dev/log"))
134
syslogger.setFormatter(logging.Formatter
135
('Mandos [%(process)d]: %(levelname)s:'
137
logger.addHandler(syslogger)
140
console = logging.StreamHandler()
141
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
145
logger.addHandler(console)
146
logger.setLevel(level)
149
class PGPError(Exception):
150
"""Exception if encryption/decryption fails"""
154
class PGPEngine(object):
155
"""A simple class for OpenPGP symmetric encryption & decryption"""
158
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
159
self.gnupgargs = ['--batch',
160
'--home', self.tempdir,
168
def __exit__(self, exc_type, exc_value, traceback):
176
if self.tempdir is not None:
177
# Delete contents of tempdir
178
for root, dirs, files in os.walk(self.tempdir,
180
for filename in files:
181
os.remove(os.path.join(root, filename))
183
os.rmdir(os.path.join(root, dirname))
185
os.rmdir(self.tempdir)
188
def password_encode(self, password):
189
# Passphrase can not be empty and can not contain newlines or
190
# NUL bytes. So we prefix it and hex encode it.
191
encoded = b"mandos" + binascii.hexlify(password)
192
if len(encoded) > 2048:
193
# GnuPG can't handle long passwords, so encode differently
194
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
195
.replace(b"\n", b"\\n")
196
.replace(b"\0", b"\\x00"))
199
def encrypt(self, data, password):
200
passphrase = self.password_encode(password)
201
with tempfile.NamedTemporaryFile(
202
dir=self.tempdir) as passfile:
203
passfile.write(passphrase)
205
proc = subprocess.Popen(['gpg', '--symmetric',
209
stdin = subprocess.PIPE,
210
stdout = subprocess.PIPE,
211
stderr = subprocess.PIPE)
212
ciphertext, err = proc.communicate(input = data)
213
if proc.returncode != 0:
217
def decrypt(self, data, password):
218
passphrase = self.password_encode(password)
219
with tempfile.NamedTemporaryFile(
220
dir = self.tempdir) as passfile:
221
passfile.write(passphrase)
223
proc = subprocess.Popen(['gpg', '--decrypt',
227
stdin = subprocess.PIPE,
228
stdout = subprocess.PIPE,
229
stderr = subprocess.PIPE)
230
decrypted_plaintext, err = proc.communicate(input = data)
231
if proc.returncode != 0:
233
return decrypted_plaintext
100
236
class AvahiError(Exception):
101
237
def __init__(self, value, *args, **kwargs):
102
238
self.value = value
103
super(AvahiError, self).__init__(value, *args, **kwargs)
104
def __unicode__(self):
105
return unicode(repr(self.value))
239
return super(AvahiError, self).__init__(value, *args,
107
243
class AvahiServiceError(AvahiError):
110
247
class AvahiGroupError(AvahiError):
190
351
dbus.UInt16(self.port),
191
352
avahi.string_array_to_txt_array(self.TXT))
192
353
self.group.Commit()
193
355
def entry_group_state_changed(self, state, error):
194
356
"""Derived from the Avahi example code"""
195
logger.debug(u"Avahi state change: %i", state)
357
logger.debug("Avahi entry group state change: %i", state)
197
359
if state == avahi.ENTRY_GROUP_ESTABLISHED:
198
logger.debug(u"Zeroconf service established.")
360
logger.debug("Zeroconf service established.")
199
361
elif state == avahi.ENTRY_GROUP_COLLISION:
200
logger.warning(u"Zeroconf service name collision.")
362
logger.info("Zeroconf service name collision.")
202
364
elif state == avahi.ENTRY_GROUP_FAILURE:
203
logger.critical(u"Avahi: Error in group state changed %s",
205
raise AvahiGroupError(u"State changed: %s"
365
logger.critical("Avahi: Error in group state changed %s",
367
raise AvahiGroupError("State changed: {!s}".format(error))
207
369
def cleanup(self):
208
370
"""Derived from the Avahi example code"""
209
371
if self.group is not None:
374
except (dbus.exceptions.UnknownMethodException,
375
dbus.exceptions.DBusException):
211
377
self.group = None
212
def server_state_changed(self, state):
380
def server_state_changed(self, state, error=None):
213
381
"""Derived from the Avahi example code"""
214
if state == avahi.SERVER_COLLISION:
215
logger.error(u"Zeroconf server name collision")
382
logger.debug("Avahi server state change: %i", state)
384
avahi.SERVER_INVALID: "Zeroconf server invalid",
385
avahi.SERVER_REGISTERING: None,
386
avahi.SERVER_COLLISION: "Zeroconf server name collision",
387
avahi.SERVER_FAILURE: "Zeroconf server failure",
389
if state in bad_states:
390
if bad_states[state] is not None:
392
logger.error(bad_states[state])
394
logger.error(bad_states[state] + ": %r", error)
217
396
elif state == avahi.SERVER_RUNNING:
400
logger.debug("Unknown state: %r", state)
402
logger.debug("Unknown state: %r: %r", state, error)
219
404
def activate(self):
220
405
"""Derived from the Avahi example code"""
221
406
if self.server is None:
222
407
self.server = dbus.Interface(
223
408
self.bus.get_object(avahi.DBUS_NAME,
224
avahi.DBUS_PATH_SERVER),
409
avahi.DBUS_PATH_SERVER,
410
follow_name_owner_changes=True),
225
411
avahi.DBUS_INTERFACE_SERVER)
226
self.server.connect_to_signal(u"StateChanged",
227
self.server_state_changed)
412
self.server.connect_to_signal("StateChanged",
413
self.server_state_changed)
228
414
self.server_state_changed(self.server.GetState())
417
class AvahiServiceToSyslog(AvahiService):
418
def rename(self, *args, **kwargs):
419
"""Add the new name to the syslog messages"""
420
ret = AvahiService.rename(self, *args, **kwargs)
421
syslogger.setFormatter(logging.Formatter(
422
'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
426
def subprocess_call_pipe(connection, # : multiprocessing.Connection
428
"""This function is meant to be called by multiprocessing.Process
430
This function runs a synchronous subprocess.call(), and writes the
431
resulting return code on the provided multiprocessing.Connection.
433
connection.send(subprocess.call(*args, **kwargs))
231
436
class Client(object):
232
437
"""A representation of a client host served by this server.
235
name: string; from the config file, used in log messages and
440
approved: bool(); 'None' if not yet approved/disapproved
441
approval_delay: datetime.timedelta(); Time to wait for approval
442
approval_duration: datetime.timedelta(); Duration of one approval
443
checker: subprocess.Popen(); a running checker process used
444
to see if the client lives.
445
'None' if no process is running.
446
checker_callback_tag: a gobject event source tag, or None
447
checker_command: string; External command which is run to check
448
if client lives. %() expansions are done at
449
runtime with vars(self) as dict, so that for
450
instance %(name)s can be used in the command.
451
checker_initiator_tag: a gobject event source tag, or None
452
created: datetime.datetime(); (UTC) object creation
453
client_structure: Object describing what attributes a client has
454
and is used for storing the client at exit
455
current_checker_command: string; current running checker_command
456
disable_initiator_tag: a gobject event source tag, or None
237
458
fingerprint: string (40 or 32 hexadecimal digits); used to
238
459
uniquely identify the client
239
secret: bytestring; sent verbatim (over TLS) to client
240
460
host: string; available for use by the checker command
241
created: datetime.datetime(); (UTC) object creation
242
last_enabled: datetime.datetime(); (UTC)
461
interval: datetime.timedelta(); How often to start a new checker
462
last_approval_request: datetime.datetime(); (UTC) or None
244
463
last_checked_ok: datetime.datetime(); (UTC) or None
464
last_checker_status: integer between 0 and 255 reflecting exit
465
status of last checker. -1 reflects crashed
466
checker, -2 means no checker completed yet.
467
last_checker_signal: The signal which killed the last checker, if
468
last_checker_status is -1
469
last_enabled: datetime.datetime(); (UTC) or None
470
name: string; from the config file, used in log messages and
472
secret: bytestring; sent verbatim (over TLS) to client
245
473
timeout: datetime.timedelta(); How long from last_checked_ok
246
474
until this client is disabled
247
interval: datetime.timedelta(); How often to start a new checker
248
disable_hook: If set, called by disable() as disable_hook(self)
249
checker: subprocess.Popen(); a running checker process used
250
to see if the client lives.
251
'None' if no process is running.
252
checker_initiator_tag: a gobject event source tag, or None
253
disable_initiator_tag: - '' -
254
checker_callback_tag: - '' -
255
checker_command: string; External command which is run to check if
256
client lives. %() expansions are done at
257
runtime with vars(self) as dict, so that for
258
instance %(name)s can be used in the command.
259
current_checker_command: string; current running checker_command
475
extended_timeout: extra long timeout when secret has been sent
476
runtime_expansions: Allowed attributes for runtime expansion.
477
expires: datetime.datetime(); time (UTC) when a client will be
479
server_settings: The server_settings dict from main()
482
runtime_expansions = ("approval_delay", "approval_duration",
483
"created", "enabled", "expires",
484
"fingerprint", "host", "interval",
485
"last_approval_request", "last_checked_ok",
486
"last_enabled", "name", "timeout")
489
"extended_timeout": "PT15M",
491
"checker": "fping -q -- %%(host)s",
493
"approval_delay": "PT0S",
494
"approval_duration": "PT1S",
495
"approved_by_default": "True",
263
def _timedelta_to_milliseconds(td):
264
"Convert a datetime.timedelta() to milliseconds"
265
return ((td.days * 24 * 60 * 60 * 1000)
266
+ (td.seconds * 1000)
267
+ (td.microseconds // 1000))
269
def timeout_milliseconds(self):
270
"Return the 'timeout' attribute in milliseconds"
271
return self._timedelta_to_milliseconds(self.timeout)
273
def interval_milliseconds(self):
274
"Return the 'interval' attribute in milliseconds"
275
return self._timedelta_to_milliseconds(self.interval)
277
def __init__(self, name = None, disable_hook=None, config=None):
278
"""Note: the 'checker' key in 'config' sets the
279
'checker_command' attribute and *not* the 'checker'
500
def config_parser(config):
501
"""Construct a new dict of client settings of this form:
502
{ client_name: {setting_name: value, ...}, ...}
503
with exceptions for any special settings as defined above.
504
NOTE: Must be a pure function. Must return the same result
505
value given the same arguments.
508
for client_name in config.sections():
509
section = dict(config.items(client_name))
510
client = settings[client_name] = {}
512
client["host"] = section["host"]
513
# Reformat values from string types to Python types
514
client["approved_by_default"] = config.getboolean(
515
client_name, "approved_by_default")
516
client["enabled"] = config.getboolean(client_name,
519
# Uppercase and remove spaces from fingerprint for later
520
# comparison purposes with return value from the
521
# fingerprint() function
522
client["fingerprint"] = (section["fingerprint"].upper()
524
if "secret" in section:
525
client["secret"] = section["secret"].decode("base64")
526
elif "secfile" in section:
527
with open(os.path.expanduser(os.path.expandvars
528
(section["secfile"])),
530
client["secret"] = secfile.read()
532
raise TypeError("No secret or secfile for section {}"
534
client["timeout"] = string_to_delta(section["timeout"])
535
client["extended_timeout"] = string_to_delta(
536
section["extended_timeout"])
537
client["interval"] = string_to_delta(section["interval"])
538
client["approval_delay"] = string_to_delta(
539
section["approval_delay"])
540
client["approval_duration"] = string_to_delta(
541
section["approval_duration"])
542
client["checker_command"] = section["checker"]
543
client["last_approval_request"] = None
544
client["last_checked_ok"] = None
545
client["last_checker_status"] = -2
549
def __init__(self, settings, name = None, server_settings=None):
284
logger.debug(u"Creating client %r", self.name)
285
# Uppercase and remove spaces from fingerprint for later
286
# comparison purposes with return value from the fingerprint()
288
self.fingerprint = (config[u"fingerprint"].upper()
290
logger.debug(u" Fingerprint: %s", self.fingerprint)
291
if u"secret" in config:
292
self.secret = config[u"secret"].decode(u"base64")
293
elif u"secfile" in config:
294
with open(os.path.expanduser(os.path.expandvars
295
(config[u"secfile"])),
297
self.secret = secfile.read()
551
if server_settings is None:
553
self.server_settings = server_settings
554
# adding all client settings
555
for setting, value in settings.items():
556
setattr(self, setting, value)
559
if not hasattr(self, "last_enabled"):
560
self.last_enabled = datetime.datetime.utcnow()
561
if not hasattr(self, "expires"):
562
self.expires = (datetime.datetime.utcnow()
299
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
302
self.created = datetime.datetime.utcnow()
304
self.last_enabled = None
305
self.last_checked_ok = None
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
308
self.disable_hook = disable_hook
565
self.last_enabled = None
568
logger.debug("Creating client %r", self.name)
569
logger.debug(" Fingerprint: %s", self.fingerprint)
570
self.created = settings.get("created",
571
datetime.datetime.utcnow())
573
# attributes specific for this server instance
309
574
self.checker = None
310
575
self.checker_initiator_tag = None
311
576
self.disable_initiator_tag = None
312
577
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
314
578
self.current_checker_command = None
315
self.last_connect = None
580
self.approvals_pending = 0
581
self.changedstate = multiprocessing_manager.Condition(
582
multiprocessing_manager.Lock())
583
self.client_structure = [attr
584
for attr in self.__dict__.iterkeys()
585
if not attr.startswith("_")]
586
self.client_structure.append("client_structure")
588
for name, t in inspect.getmembers(
589
type(self), lambda obj: isinstance(obj, property)):
590
if not name.startswith("_"):
591
self.client_structure.append(name)
593
# Send notice to process children that client state has changed
594
def send_changedstate(self):
595
with self.changedstate:
596
self.changedstate.notify_all()
317
598
def enable(self):
318
599
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
600
if getattr(self, "enabled", False):
320
601
# Already enabled
603
self.expires = datetime.datetime.utcnow() + self.timeout
322
605
self.last_enabled = datetime.datetime.utcnow()
323
# Schedule a new checker to be started an 'interval' from now,
324
# and every interval from then on.
325
self.checker_initiator_tag = (gobject.timeout_add
326
(self.interval_milliseconds(),
328
# Schedule a disable() when 'timeout' has passed
329
self.disable_initiator_tag = (gobject.timeout_add
330
(self.timeout_milliseconds(),
333
# Also start a new checker *right now*.
607
self.send_changedstate()
336
609
def disable(self, quiet=True):
337
610
"""Disable this client."""
338
611
if not getattr(self, "enabled", False):
341
logger.info(u"Disabling client %s", self.name)
342
if getattr(self, u"disable_initiator_tag", False):
614
logger.info("Disabling client %s", self.name)
615
if getattr(self, "disable_initiator_tag", None) is not None:
343
616
gobject.source_remove(self.disable_initiator_tag)
344
617
self.disable_initiator_tag = None
345
if getattr(self, u"checker_initiator_tag", False):
619
if getattr(self, "checker_initiator_tag", None) is not None:
346
620
gobject.source_remove(self.checker_initiator_tag)
347
621
self.checker_initiator_tag = None
348
622
self.stop_checker()
349
if self.disable_hook:
350
self.disable_hook(self)
351
623
self.enabled = False
625
self.send_changedstate()
352
626
# Do not run this again if called by a gobject.timeout_add
355
629
def __del__(self):
356
self.disable_hook = None
359
def checker_callback(self, pid, condition, command):
632
def init_checker(self):
633
# Schedule a new checker to be started an 'interval' from now,
634
# and every interval from then on.
635
if self.checker_initiator_tag is not None:
636
gobject.source_remove(self.checker_initiator_tag)
637
self.checker_initiator_tag = gobject.timeout_add(
638
int(self.interval.total_seconds() * 1000),
640
# Schedule a disable() when 'timeout' has passed
641
if self.disable_initiator_tag is not None:
642
gobject.source_remove(self.disable_initiator_tag)
643
self.disable_initiator_tag = gobject.timeout_add(
644
int(self.timeout.total_seconds() * 1000), self.disable)
645
# Also start a new checker *right now*.
648
def checker_callback(self, source, condition,
649
(connection, command)):
360
650
"""The checker has completed, so take appropriate actions."""
361
651
self.checker_callback_tag = None
362
652
self.checker = None
363
if os.WIFEXITED(condition):
364
exitstatus = os.WEXITSTATUS(condition)
366
logger.info(u"Checker for %(name)s succeeded",
653
# Read return code from connection (see subprocess_call_pipe)
654
returncode = connection.recv()
658
self.last_checker_status = returncode
659
self.last_checker_signal = None
660
if self.last_checker_status == 0:
661
logger.info("Checker for %(name)s succeeded",
368
663
self.checked_ok()
370
logger.info(u"Checker for %(name)s failed",
665
logger.info("Checker for %(name)s failed", vars(self))
373
logger.warning(u"Checker for %(name)s crashed?",
667
self.last_checker_status = -1
668
self.last_checker_signal = -returncode
669
logger.warning("Checker for %(name)s crashed?",
376
673
def checked_ok(self):
377
"""Bump up the timeout for this client.
379
This should only be called when the client has been seen,
674
"""Assert that the client has been seen, alive and well."""
382
675
self.last_checked_ok = datetime.datetime.utcnow()
383
gobject.source_remove(self.disable_initiator_tag)
384
self.disable_initiator_tag = (gobject.timeout_add
385
(self.timeout_milliseconds(),
676
self.last_checker_status = 0
677
self.last_checker_signal = None
680
def bump_timeout(self, timeout=None):
681
"""Bump up the timeout for this client."""
683
timeout = self.timeout
684
if self.disable_initiator_tag is not None:
685
gobject.source_remove(self.disable_initiator_tag)
686
self.disable_initiator_tag = None
687
if getattr(self, "enabled", False):
688
self.disable_initiator_tag = gobject.timeout_add(
689
int(timeout.total_seconds() * 1000), self.disable)
690
self.expires = datetime.datetime.utcnow() + timeout
692
def need_approval(self):
693
self.last_approval_request = datetime.datetime.utcnow()
388
695
def start_checker(self):
389
696
"""Start a new checker subprocess if one is not running.
391
698
If a checker already exists, leave it running and do
393
700
# The reason for not killing a running checker is that if we
394
# did that, then if a checker (for some reason) started
395
# running slowly and taking more than 'interval' time, the
396
# client would inevitably timeout, since no checker would get
397
# a chance to run to completion. If we instead leave running
701
# did that, and if a checker (for some reason) started running
702
# slowly and taking more than 'interval' time, then the client
703
# would inevitably timeout, since no checker would get a
704
# chance to run to completion. If we instead leave running
398
705
# checkers alone, the checker would have to take more time
399
706
# than 'timeout' for the client to be disabled, which is as it
402
# If a checker exists, make sure it is not a zombie
404
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
405
except (AttributeError, OSError), error:
406
if (isinstance(error, OSError)
407
and error.errno != errno.ECHILD):
411
logger.warning(u"Checker was a zombie")
412
gobject.source_remove(self.checker_callback_tag)
413
self.checker_callback(pid, status,
414
self.current_checker_command)
709
if self.checker is not None and not self.checker.is_alive():
710
logger.warning("Checker was not alive; joining")
415
713
# Start a new checker if needed
416
714
if self.checker is None:
715
# Escape attributes for the shell
717
attr: re.escape(str(getattr(self, attr)))
718
for attr in self.runtime_expansions }
418
# In case checker_command has exactly one % operator
419
command = self.checker_command % self.host
421
# Escape attributes for the shell
422
escaped_attrs = dict((key,
423
re.escape(unicode(str(val),
427
vars(self).iteritems())
429
command = self.checker_command % escaped_attrs
430
except TypeError, error:
431
logger.error(u'Could not format string "%s":'
432
u' %s', self.checker_command, error)
433
return True # Try again later
720
command = self.checker_command % escaped_attrs
721
except TypeError as error:
722
logger.error('Could not format string "%s"',
723
self.checker_command,
725
return True # Try again later
434
726
self.current_checker_command = command
436
logger.info(u"Starting checker %r for %s",
438
# We don't need to redirect stdout and stderr, since
439
# in normal mode, that is already done by daemon(),
440
# and in debug mode we don't want to. (Stdin is
441
# always replaced by /dev/null.)
442
self.checker = subprocess.Popen(command,
444
shell=True, cwd=u"/")
445
self.checker_callback_tag = (gobject.child_watch_add
447
self.checker_callback,
449
# The checker may have completed before the gobject
450
# watch was added. Check for this.
451
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
453
gobject.source_remove(self.checker_callback_tag)
454
self.checker_callback(pid, status, command)
455
except OSError, error:
456
logger.error(u"Failed to start subprocess: %s",
727
logger.info("Starting checker %r for %s", command,
729
# We don't need to redirect stdout and stderr, since
730
# in normal mode, that is already done by daemon(),
731
# and in debug mode we don't want to. (Stdin is
732
# always replaced by /dev/null.)
733
# The exception is when not debugging but nevertheless
734
# running in the foreground; use the previously
736
popen_args = { "close_fds": True,
739
if (not self.server_settings["debug"]
740
and self.server_settings["foreground"]):
741
popen_args.update({"stdout": wnull,
743
pipe = multiprocessing.Pipe(duplex=False)
744
self.checker = multiprocessing.Process(
745
target=subprocess_call_pipe, args=(pipe[1], command),
748
self.checker_callback_tag = gobject.io_add_watch(
749
pipe[0].fileno(), gobject.IO_IN,
750
self.checker_callback, (pipe[0], command))
458
751
# Re-run this periodically if run by gobject.timeout_add
529
859
class DBusObjectWithProperties(dbus.service.Object):
530
860
"""A D-Bus object with properties.
532
862
Classes inheriting from this can use the dbus_service_property
533
863
decorator to expose methods as D-Bus properties. It exposes the
534
864
standard Get(), Set(), and GetAll() methods on the D-Bus.
538
def _is_dbus_property(obj):
539
return getattr(obj, u"_dbus_is_property", False)
868
def _is_dbus_thing(thing):
869
"""Returns a function testing if an attribute is a D-Bus thing
871
If called like _is_dbus_thing("method") it returns a function
872
suitable for use as predicate to inspect.getmembers().
874
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
541
def _get_all_dbus_properties(self):
877
def _get_all_dbus_things(self, thing):
542
878
"""Returns a generator of (name, attribute) pairs
544
return ((prop._dbus_name, prop)
546
inspect.getmembers(self, self._is_dbus_property))
880
return ((getattr(athing.__get__(self), "_dbus_name", name),
881
athing.__get__(self))
882
for cls in self.__class__.__mro__
884
inspect.getmembers(cls, self._is_dbus_thing(thing)))
548
886
def _get_dbus_property(self, interface_name, property_name):
549
887
"""Returns a bound method if one exists which is a D-Bus
550
888
property with the specified name and interface.
552
for name in (property_name,
553
property_name + u"_dbus_property"):
554
prop = getattr(self, name, None)
556
or not self._is_dbus_property(prop)
557
or prop._dbus_name != property_name
558
or (interface_name and prop._dbus_interface
559
and interface_name != prop._dbus_interface)):
890
for cls in self.__class__.__mro__:
891
for name, value in inspect.getmembers(
892
cls, self._is_dbus_thing("property")):
893
if (value._dbus_name == property_name
894
and value._dbus_interface == interface_name):
895
return value.__get__(self)
562
897
# No such property
563
raise DBusPropertyNotFound(self.dbus_object_path + u":"
564
+ interface_name + u"."
898
raise DBusPropertyNotFound("{}:{}.{}".format(
899
self.dbus_object_path, interface_name, property_name))
567
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
901
@dbus.service.method(dbus.PROPERTIES_IFACE,
569
904
def Get(self, interface_name, property_name):
570
905
"""Standard D-Bus property Get() method, see D-Bus standard.
572
907
prop = self._get_dbus_property(interface_name, property_name)
573
if prop._dbus_access == u"write":
908
if prop._dbus_access == "write":
574
909
raise DBusPropertyAccessException(property_name)
576
if not hasattr(value, u"variant_level"):
911
if not hasattr(value, "variant_level"):
578
913
return type(value)(value, variant_level=value.variant_level+1)
580
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
915
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
581
916
def Set(self, interface_name, property_name, value):
582
917
"""Standard D-Bus property Set() method, see D-Bus standard.
584
919
prop = self._get_dbus_property(interface_name, property_name)
585
if prop._dbus_access == u"read":
920
if prop._dbus_access == "read":
586
921
raise DBusPropertyAccessException(property_name)
587
if prop._dbus_get_args_options[u"byte_arrays"]:
922
if prop._dbus_get_args_options["byte_arrays"]:
588
923
# The byte_arrays option is not supported yet on
589
924
# signatures other than "ay".
590
if prop._dbus_signature != u"ay":
592
value = dbus.ByteArray(''.join(unichr(byte)
925
if prop._dbus_signature != "ay":
926
raise ValueError("Byte arrays not supported for non-"
927
"'ay' signature {!r}"
928
.format(prop._dbus_signature))
929
value = dbus.ByteArray(b''.join(chr(byte)
596
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
out_signature=u"a{sv}")
933
@dbus.service.method(dbus.PROPERTIES_IFACE,
935
out_signature="a{sv}")
598
936
def GetAll(self, interface_name):
599
937
"""Standard D-Bus property GetAll() method, see D-Bus
602
940
Note: Will not include properties with access="write".
605
for name, prop in self._get_all_dbus_properties():
943
for name, prop in self._get_all_dbus_things("property"):
606
944
if (interface_name
607
945
and interface_name != prop._dbus_interface):
608
946
# Interface non-empty but did not match
610
948
# Ignore write-only properties
611
if prop._dbus_access == u"write":
949
if prop._dbus_access == "write":
614
if not hasattr(value, u"variant_level"):
952
if not hasattr(value, "variant_level"):
953
properties[name] = value
617
all[name] = type(value)(value, variant_level=
618
value.variant_level+1)
619
return dbus.Dictionary(all, signature=u"sv")
955
properties[name] = type(value)(
956
value, variant_level = value.variant_level + 1)
957
return dbus.Dictionary(properties, signature="sv")
959
@dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
960
def PropertiesChanged(self, interface_name, changed_properties,
961
invalidated_properties):
962
"""Standard D-Bus PropertiesChanged() signal, see D-Bus
621
967
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
623
969
path_keyword='object_path',
624
970
connection_keyword='connection')
625
971
def Introspect(self, object_path, connection):
626
"""Standard D-Bus method, overloaded to insert property tags.
972
"""Overloading of standard D-Bus method.
974
Inserts property tags and interface annotation tags.
628
976
xmlstring = dbus.service.Object.Introspect(self, object_path,
631
979
document = xml.dom.minidom.parseString(xmlstring)
632
981
def make_tag(document, name, prop):
633
e = document.createElement(u"property")
634
e.setAttribute(u"name", name)
635
e.setAttribute(u"type", prop._dbus_signature)
636
e.setAttribute(u"access", prop._dbus_access)
982
e = document.createElement("property")
983
e.setAttribute("name", name)
984
e.setAttribute("type", prop._dbus_signature)
985
e.setAttribute("access", prop._dbus_access)
638
for if_tag in document.getElementsByTagName(u"interface"):
988
for if_tag in document.getElementsByTagName("interface"):
639
990
for tag in (make_tag(document, name, prop)
641
in self._get_all_dbus_properties()
992
in self._get_all_dbus_things("property")
642
993
if prop._dbus_interface
643
== if_tag.getAttribute(u"name")):
994
== if_tag.getAttribute("name")):
644
995
if_tag.appendChild(tag)
996
# Add annotation tags
997
for typ in ("method", "signal", "property"):
998
for tag in if_tag.getElementsByTagName(typ):
1000
for name, prop in (self.
1001
_get_all_dbus_things(typ)):
1002
if (name == tag.getAttribute("name")
1003
and prop._dbus_interface
1004
== if_tag.getAttribute("name")):
1005
annots.update(getattr(
1006
prop, "_dbus_annotations", {}))
1007
for name, value in annots.items():
1008
ann_tag = document.createElement(
1010
ann_tag.setAttribute("name", name)
1011
ann_tag.setAttribute("value", value)
1012
tag.appendChild(ann_tag)
1013
# Add interface annotation tags
1014
for annotation, value in dict(
1015
itertools.chain.from_iterable(
1016
annotations().items()
1017
for name, annotations
1018
in self._get_all_dbus_things("interface")
1019
if name == if_tag.getAttribute("name")
1021
ann_tag = document.createElement("annotation")
1022
ann_tag.setAttribute("name", annotation)
1023
ann_tag.setAttribute("value", value)
1024
if_tag.appendChild(ann_tag)
645
1025
# Add the names to the return values for the
646
1026
# "org.freedesktop.DBus.Properties" methods
647
if (if_tag.getAttribute(u"name")
648
== u"org.freedesktop.DBus.Properties"):
649
for cn in if_tag.getElementsByTagName(u"method"):
650
if cn.getAttribute(u"name") == u"Get":
651
for arg in cn.getElementsByTagName(u"arg"):
652
if (arg.getAttribute(u"direction")
654
arg.setAttribute(u"name", u"value")
655
elif cn.getAttribute(u"name") == u"GetAll":
656
for arg in cn.getElementsByTagName(u"arg"):
657
if (arg.getAttribute(u"direction")
659
arg.setAttribute(u"name", u"props")
660
xmlstring = document.toxml(u"utf-8")
1027
if (if_tag.getAttribute("name")
1028
== "org.freedesktop.DBus.Properties"):
1029
for cn in if_tag.getElementsByTagName("method"):
1030
if cn.getAttribute("name") == "Get":
1031
for arg in cn.getElementsByTagName("arg"):
1032
if (arg.getAttribute("direction")
1034
arg.setAttribute("name", "value")
1035
elif cn.getAttribute("name") == "GetAll":
1036
for arg in cn.getElementsByTagName("arg"):
1037
if (arg.getAttribute("direction")
1039
arg.setAttribute("name", "props")
1040
xmlstring = document.toxml("utf-8")
661
1041
document.unlink()
662
1042
except (AttributeError, xml.dom.DOMException,
663
xml.parsers.expat.ExpatError), error:
664
logger.error(u"Failed to override Introspection method",
1043
xml.parsers.expat.ExpatError) as error:
1044
logger.error("Failed to override Introspection method",
666
1046
return xmlstring
1049
def datetime_to_dbus(dt, variant_level=0):
1050
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1052
return dbus.String("", variant_level = variant_level)
1053
return dbus.String(dt.isoformat(), variant_level=variant_level)
1056
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1057
"""A class decorator; applied to a subclass of
1058
dbus.service.Object, it will add alternate D-Bus attributes with
1059
interface names according to the "alt_interface_names" mapping.
1062
@alternate_dbus_interfaces({"org.example.Interface":
1063
"net.example.AlternateInterface"})
1064
class SampleDBusObject(dbus.service.Object):
1065
@dbus.service.method("org.example.Interface")
1066
def SampleDBusMethod():
1069
The above "SampleDBusMethod" on "SampleDBusObject" will be
1070
reachable via two interfaces: "org.example.Interface" and
1071
"net.example.AlternateInterface", the latter of which will have
1072
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1073
"true", unless "deprecate" is passed with a False value.
1075
This works for methods and signals, and also for D-Bus properties
1076
(from DBusObjectWithProperties) and interfaces (from the
1077
dbus_interface_annotations decorator).
1081
for orig_interface_name, alt_interface_name in (
1082
alt_interface_names.items()):
1084
interface_names = set()
1085
# Go though all attributes of the class
1086
for attrname, attribute in inspect.getmembers(cls):
1087
# Ignore non-D-Bus attributes, and D-Bus attributes
1088
# with the wrong interface name
1089
if (not hasattr(attribute, "_dbus_interface")
1090
or not attribute._dbus_interface.startswith(
1091
orig_interface_name)):
1093
# Create an alternate D-Bus interface name based on
1095
alt_interface = attribute._dbus_interface.replace(
1096
orig_interface_name, alt_interface_name)
1097
interface_names.add(alt_interface)
1098
# Is this a D-Bus signal?
1099
if getattr(attribute, "_dbus_is_signal", False):
1100
# Extract the original non-method undecorated
1101
# function by black magic
1102
nonmethod_func = (dict(
1103
zip(attribute.func_code.co_freevars,
1104
attribute.__closure__))
1105
["func"].cell_contents)
1106
# Create a new, but exactly alike, function
1107
# object, and decorate it to be a new D-Bus signal
1108
# with the alternate D-Bus interface name
1109
new_function = (dbus.service.signal(
1110
alt_interface, attribute._dbus_signature)
1111
(types.FunctionType(
1112
nonmethod_func.func_code,
1113
nonmethod_func.func_globals,
1114
nonmethod_func.func_name,
1115
nonmethod_func.func_defaults,
1116
nonmethod_func.func_closure)))
1117
# Copy annotations, if any
1119
new_function._dbus_annotations = dict(
1120
attribute._dbus_annotations)
1121
except AttributeError:
1123
# Define a creator of a function to call both the
1124
# original and alternate functions, so both the
1125
# original and alternate signals gets sent when
1126
# the function is called
1127
def fixscope(func1, func2):
1128
"""This function is a scope container to pass
1129
func1 and func2 to the "call_both" function
1130
outside of its arguments"""
1132
def call_both(*args, **kwargs):
1133
"""This function will emit two D-Bus
1134
signals by calling func1 and func2"""
1135
func1(*args, **kwargs)
1136
func2(*args, **kwargs)
1139
# Create the "call_both" function and add it to
1141
attr[attrname] = fixscope(attribute, new_function)
1142
# Is this a D-Bus method?
1143
elif getattr(attribute, "_dbus_is_method", False):
1144
# Create a new, but exactly alike, function
1145
# object. Decorate it to be a new D-Bus method
1146
# with the alternate D-Bus interface name. Add it
1149
dbus.service.method(
1151
attribute._dbus_in_signature,
1152
attribute._dbus_out_signature)
1153
(types.FunctionType(attribute.func_code,
1154
attribute.func_globals,
1155
attribute.func_name,
1156
attribute.func_defaults,
1157
attribute.func_closure)))
1158
# Copy annotations, if any
1160
attr[attrname]._dbus_annotations = dict(
1161
attribute._dbus_annotations)
1162
except AttributeError:
1164
# Is this a D-Bus property?
1165
elif getattr(attribute, "_dbus_is_property", False):
1166
# Create a new, but exactly alike, function
1167
# object, and decorate it to be a new D-Bus
1168
# property with the alternate D-Bus interface
1169
# name. Add it to the class.
1170
attr[attrname] = (dbus_service_property(
1171
alt_interface, attribute._dbus_signature,
1172
attribute._dbus_access,
1173
attribute._dbus_get_args_options
1175
(types.FunctionType(
1176
attribute.func_code,
1177
attribute.func_globals,
1178
attribute.func_name,
1179
attribute.func_defaults,
1180
attribute.func_closure)))
1181
# Copy annotations, if any
1183
attr[attrname]._dbus_annotations = dict(
1184
attribute._dbus_annotations)
1185
except AttributeError:
1187
# Is this a D-Bus interface?
1188
elif getattr(attribute, "_dbus_is_interface", False):
1189
# Create a new, but exactly alike, function
1190
# object. Decorate it to be a new D-Bus interface
1191
# with the alternate D-Bus interface name. Add it
1194
dbus_interface_annotations(alt_interface)
1195
(types.FunctionType(attribute.func_code,
1196
attribute.func_globals,
1197
attribute.func_name,
1198
attribute.func_defaults,
1199
attribute.func_closure)))
1201
# Deprecate all alternate interfaces
1202
iname="_AlternateDBusNames_interface_annotation{}"
1203
for interface_name in interface_names:
1205
@dbus_interface_annotations(interface_name)
1207
return { "org.freedesktop.DBus.Deprecated":
1209
# Find an unused name
1210
for aname in (iname.format(i)
1211
for i in itertools.count()):
1212
if aname not in attr:
1216
# Replace the class with a new subclass of it with
1217
# methods, signals, etc. as created above.
1218
cls = type(b"{}Alternate".format(cls.__name__),
1225
@alternate_dbus_interfaces({"se.recompile.Mandos":
1226
"se.bsnet.fukt.Mandos"})
669
1227
class ClientDBus(Client, DBusObjectWithProperties):
670
1228
"""A Client class using D-Bus
680
1244
Client.__init__(self, *args, **kwargs)
681
1245
# Only now, when this client is initialized, can it show up on
683
self.dbus_object_path = (dbus.ObjectPath
685
+ self.name.replace(u".", u"_")))
1247
client_object_name = str(self.name).translate(
1248
{ord("."): ord("_"),
1249
ord("-"): ord("_")})
1250
self.dbus_object_path = dbus.ObjectPath(
1251
"/clients/" + client_object_name)
686
1252
DBusObjectWithProperties.__init__(self, self.bus,
687
1253
self.dbus_object_path)
690
def _datetime_to_dbus(dt, variant_level=0):
691
"""Convert a UTC datetime.datetime() to a D-Bus type."""
692
return dbus.String(dt.isoformat(),
693
variant_level=variant_level)
696
oldstate = getattr(self, u"enabled", False)
697
r = Client.enable(self)
698
if oldstate != self.enabled:
700
self.PropertyChanged(dbus.String(u"enabled"),
701
dbus.Boolean(True, variant_level=1))
702
self.PropertyChanged(
703
dbus.String(u"last_enabled"),
704
self._datetime_to_dbus(self.last_enabled,
708
def disable(self, quiet = False):
709
oldstate = getattr(self, u"enabled", False)
710
r = Client.disable(self, quiet=quiet)
711
if not quiet and oldstate != self.enabled:
713
self.PropertyChanged(dbus.String(u"enabled"),
714
dbus.Boolean(False, variant_level=1))
1255
def notifychangeproperty(transform_func, dbus_name,
1256
type_func=lambda x: x,
1258
invalidate_only=False,
1259
_interface=_interface):
1260
""" Modify a variable so that it's a property which announces
1261
its changes to DBus.
1263
transform_fun: Function that takes a value and a variant_level
1264
and transforms it to a D-Bus type.
1265
dbus_name: D-Bus name of the variable
1266
type_func: Function that transform the value before sending it
1267
to the D-Bus. Default: no transform
1268
variant_level: D-Bus variant level. Default: 1
1270
attrname = "_{}".format(dbus_name)
1272
def setter(self, value):
1273
if hasattr(self, "dbus_object_path"):
1274
if (not hasattr(self, attrname) or
1275
type_func(getattr(self, attrname, None))
1276
!= type_func(value)):
1278
self.PropertiesChanged(
1279
_interface, dbus.Dictionary(),
1280
dbus.Array((dbus_name, )))
1282
dbus_value = transform_func(
1284
variant_level = variant_level)
1285
self.PropertyChanged(dbus.String(dbus_name),
1287
self.PropertiesChanged(
1289
dbus.Dictionary({ dbus.String(dbus_name):
1292
setattr(self, attrname, value)
1294
return property(lambda self: getattr(self, attrname), setter)
1296
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1297
approvals_pending = notifychangeproperty(dbus.Boolean,
1300
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1301
last_enabled = notifychangeproperty(datetime_to_dbus,
1303
checker = notifychangeproperty(
1304
dbus.Boolean, "CheckerRunning",
1305
type_func = lambda checker: checker is not None)
1306
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1308
last_checker_status = notifychangeproperty(dbus.Int16,
1309
"LastCheckerStatus")
1310
last_approval_request = notifychangeproperty(
1311
datetime_to_dbus, "LastApprovalRequest")
1312
approved_by_default = notifychangeproperty(dbus.Boolean,
1313
"ApprovedByDefault")
1314
approval_delay = notifychangeproperty(
1315
dbus.UInt64, "ApprovalDelay",
1316
type_func = lambda td: td.total_seconds() * 1000)
1317
approval_duration = notifychangeproperty(
1318
dbus.UInt64, "ApprovalDuration",
1319
type_func = lambda td: td.total_seconds() * 1000)
1320
host = notifychangeproperty(dbus.String, "Host")
1321
timeout = notifychangeproperty(
1322
dbus.UInt64, "Timeout",
1323
type_func = lambda td: td.total_seconds() * 1000)
1324
extended_timeout = notifychangeproperty(
1325
dbus.UInt64, "ExtendedTimeout",
1326
type_func = lambda td: td.total_seconds() * 1000)
1327
interval = notifychangeproperty(
1328
dbus.UInt64, "Interval",
1329
type_func = lambda td: td.total_seconds() * 1000)
1330
checker_command = notifychangeproperty(dbus.String, "Checker")
1331
secret = notifychangeproperty(dbus.ByteArray, "Secret",
1332
invalidate_only=True)
1334
del notifychangeproperty
717
1336
def __del__(self, *args, **kwargs):
719
1338
self.remove_from_connection()
720
1339
except LookupError:
722
if hasattr(DBusObjectWithProperties, u"__del__"):
1341
if hasattr(DBusObjectWithProperties, "__del__"):
723
1342
DBusObjectWithProperties.__del__(self, *args, **kwargs)
724
1343
Client.__del__(self, *args, **kwargs)
726
def checker_callback(self, pid, condition, command,
728
self.checker_callback_tag = None
731
self.PropertyChanged(dbus.String(u"checker_running"),
732
dbus.Boolean(False, variant_level=1))
733
if os.WIFEXITED(condition):
734
exitstatus = os.WEXITSTATUS(condition)
1345
def checker_callback(self, source, condition,
1346
(connection, command), *args, **kwargs):
1347
ret = Client.checker_callback(self, source, condition,
1348
(connection, command), *args,
1350
exitstatus = self.last_checker_status
735
1352
# Emit D-Bus signal
736
1353
self.CheckerCompleted(dbus.Int16(exitstatus),
737
dbus.Int64(condition),
738
1355
dbus.String(command))
740
1357
# Emit D-Bus signal
741
1358
self.CheckerCompleted(dbus.Int16(-1),
742
dbus.Int64(condition),
1360
self.last_checker_signal),
743
1361
dbus.String(command))
745
return Client.checker_callback(self, pid, condition, command,
748
def checked_ok(self, *args, **kwargs):
749
r = Client.checked_ok(self, *args, **kwargs)
751
self.PropertyChanged(
752
dbus.String(u"last_checked_ok"),
753
(self._datetime_to_dbus(self.last_checked_ok,
757
1364
def start_checker(self, *args, **kwargs):
758
old_checker = self.checker
759
if self.checker is not None:
760
old_checker_pid = self.checker.pid
762
old_checker_pid = None
1365
old_checker_pid = getattr(self.checker, "pid", None)
763
1366
r = Client.start_checker(self, *args, **kwargs)
764
1367
# Only if new checker process was started
765
1368
if (self.checker is not None
766
1369
and old_checker_pid != self.checker.pid):
767
1370
# Emit D-Bus signal
768
1371
self.CheckerStarted(self.current_checker_command)
769
self.PropertyChanged(
770
dbus.String(u"checker_running"),
771
dbus.Boolean(True, variant_level=1))
774
def stop_checker(self, *args, **kwargs):
775
old_checker = getattr(self, u"checker", None)
776
r = Client.stop_checker(self, *args, **kwargs)
777
if (old_checker is not None
778
and getattr(self, u"checker", None) is None):
779
self.PropertyChanged(dbus.String(u"checker_running"),
780
dbus.Boolean(False, variant_level=1))
1374
def _reset_approved(self):
1375
self.approved = None
1378
def approve(self, value=True):
1379
self.approved = value
1380
gobject.timeout_add(int(self.approval_duration.total_seconds()
1381
* 1000), self._reset_approved)
1382
self.send_changedstate()
783
1384
## D-Bus methods, signals & properties
784
_interface = u"se.bsnet.fukt.Mandos.Client"
788
1390
# CheckerCompleted - signal
789
@dbus.service.signal(_interface, signature=u"nxs")
1391
@dbus.service.signal(_interface, signature="nxs")
790
1392
def CheckerCompleted(self, exitcode, waitstatus, command):
794
1396
# CheckerStarted - signal
795
@dbus.service.signal(_interface, signature=u"s")
1397
@dbus.service.signal(_interface, signature="s")
796
1398
def CheckerStarted(self, command):
800
1402
# PropertyChanged - signal
801
@dbus.service.signal(_interface, signature=u"sv")
1403
@dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1404
@dbus.service.signal(_interface, signature="sv")
802
1405
def PropertyChanged(self, property, value):
894
# last_checked_ok - property
895
@dbus_service_property(_interface, signature=u"s",
897
def last_checked_ok_dbus_property(self, value=None):
1542
# LastCheckedOK - property
1543
@dbus_service_property(_interface,
1546
def LastCheckedOK_dbus_property(self, value=None):
898
1547
if value is not None:
899
1548
self.checked_ok()
901
if self.last_checked_ok is None:
902
return dbus.String(u"")
903
return dbus.String(self._datetime_to_dbus(self
907
@dbus_service_property(_interface, signature=u"t",
909
def timeout_dbus_property(self, value=None):
1550
return datetime_to_dbus(self.last_checked_ok)
1552
# LastCheckerStatus - property
1553
@dbus_service_property(_interface, signature="n", access="read")
1554
def LastCheckerStatus_dbus_property(self):
1555
return dbus.Int16(self.last_checker_status)
1557
# Expires - property
1558
@dbus_service_property(_interface, signature="s", access="read")
1559
def Expires_dbus_property(self):
1560
return datetime_to_dbus(self.expires)
1562
# LastApprovalRequest - property
1563
@dbus_service_property(_interface, signature="s", access="read")
1564
def LastApprovalRequest_dbus_property(self):
1565
return datetime_to_dbus(self.last_approval_request)
1567
# Timeout - property
1568
@dbus_service_property(_interface,
1571
def Timeout_dbus_property(self, value=None):
910
1572
if value is None: # get
911
return dbus.UInt64(self.timeout_milliseconds())
1573
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1574
old_timeout = self.timeout
912
1575
self.timeout = datetime.timedelta(0, 0, 0, value)
914
self.PropertyChanged(dbus.String(u"timeout"),
915
dbus.UInt64(value, variant_level=1))
916
if getattr(self, u"disable_initiator_tag", None) is None:
919
gobject.source_remove(self.disable_initiator_tag)
920
self.disable_initiator_tag = None
922
_timedelta_to_milliseconds((self
928
# The timeout has passed
931
self.disable_initiator_tag = (gobject.timeout_add
932
(time_to_die, self.disable))
934
# interval - property
935
@dbus_service_property(_interface, signature=u"t",
937
def interval_dbus_property(self, value=None):
938
if value is None: # get
939
return dbus.UInt64(self.interval_milliseconds())
1576
# Reschedule disabling
1578
now = datetime.datetime.utcnow()
1579
self.expires += self.timeout - old_timeout
1580
if self.expires <= now:
1581
# The timeout has passed
1584
if (getattr(self, "disable_initiator_tag", None)
1587
gobject.source_remove(self.disable_initiator_tag)
1588
self.disable_initiator_tag = gobject.timeout_add(
1589
int((self.expires - now).total_seconds() * 1000),
1592
# ExtendedTimeout - property
1593
@dbus_service_property(_interface,
1596
def ExtendedTimeout_dbus_property(self, value=None):
1597
if value is None: # get
1598
return dbus.UInt64(self.extended_timeout.total_seconds()
1600
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1602
# Interval - property
1603
@dbus_service_property(_interface,
1606
def Interval_dbus_property(self, value=None):
1607
if value is None: # get
1608
return dbus.UInt64(self.interval.total_seconds() * 1000)
940
1609
self.interval = datetime.timedelta(0, 0, 0, value)
942
self.PropertyChanged(dbus.String(u"interval"),
943
dbus.UInt64(value, variant_level=1))
944
if getattr(self, u"checker_initiator_tag", None) is None:
1610
if getattr(self, "checker_initiator_tag", None) is None:
946
# Reschedule checker run
947
gobject.source_remove(self.checker_initiator_tag)
948
self.checker_initiator_tag = (gobject.timeout_add
949
(value, self.start_checker))
950
self.start_checker() # Start one now, too
953
@dbus_service_property(_interface, signature=u"s",
955
def checker_dbus_property(self, value=None):
1613
# Reschedule checker run
1614
gobject.source_remove(self.checker_initiator_tag)
1615
self.checker_initiator_tag = gobject.timeout_add(
1616
value, self.start_checker)
1617
self.start_checker() # Start one now, too
1619
# Checker - property
1620
@dbus_service_property(_interface,
1623
def Checker_dbus_property(self, value=None):
956
1624
if value is None: # get
957
1625
return dbus.String(self.checker_command)
958
self.checker_command = value
960
self.PropertyChanged(dbus.String(u"checker"),
961
dbus.String(self.checker_command,
1626
self.checker_command = str(value)
964
# checker_running - property
965
@dbus_service_property(_interface, signature=u"b",
967
def checker_running_dbus_property(self, value=None):
1628
# CheckerRunning - property
1629
@dbus_service_property(_interface,
1632
def CheckerRunning_dbus_property(self, value=None):
968
1633
if value is None: # get
969
1634
return dbus.Boolean(self.checker is not None)
993
1688
Note: This will run in its own forked process."""
995
1690
def handle(self):
996
logger.info(u"TCP connection from: %s",
997
unicode(self.client_address))
998
logger.debug(u"IPC Pipe FD: %d",
999
self.server.child_pipe[1].fileno())
1000
# Open IPC pipe to parent process
1001
with contextlib.nested(self.server.child_pipe[1],
1002
self.server.parent_pipe[0]
1003
) as (ipc, ipc_return):
1004
session = (gnutls.connection
1005
.ClientSession(self.request,
1007
.X509Credentials()))
1691
with contextlib.closing(self.server.child_pipe) as child_pipe:
1692
logger.info("TCP connection from: %s",
1693
str(self.client_address))
1694
logger.debug("Pipe FD: %d",
1695
self.server.child_pipe.fileno())
1009
line = self.request.makefile().readline()
1010
logger.debug(u"Protocol version: %r", line)
1012
if int(line.strip().split()[0]) > 1:
1014
except (ValueError, IndexError, RuntimeError), error:
1015
logger.error(u"Unknown protocol version: %s", error)
1697
session = gnutls.connection.ClientSession(
1698
self.request, gnutls.connection .X509Credentials())
1018
1700
# Note: gnutls.connection.X509Credentials is really a
1019
1701
# generic GnuTLS certificate credentials object so long as
1020
1702
# no X.509 keys are added to it. Therefore, we can use it
1021
1703
# here despite using OpenPGP certificates.
1023
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1024
# u"+AES-256-CBC", u"+SHA1",
1025
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1705
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1706
# "+AES-256-CBC", "+SHA1",
1707
# "+COMP-NULL", "+CTYPE-OPENPGP",
1027
1709
# Use a fallback default, since this MUST be set.
1028
1710
priority = self.server.gnutls_priority
1029
1711
if priority is None:
1030
priority = u"NORMAL"
1031
(gnutls.library.functions
1032
.gnutls_priority_set_direct(session._c_object,
1713
gnutls.library.functions.gnutls_priority_set_direct(
1714
session._c_object, priority, None)
1716
# Start communication using the Mandos protocol
1717
# Get protocol number
1718
line = self.request.makefile().readline()
1719
logger.debug("Protocol version: %r", line)
1721
if int(line.strip().split()[0]) > 1:
1722
raise RuntimeError(line)
1723
except (ValueError, IndexError, RuntimeError) as error:
1724
logger.error("Unknown protocol version: %s", error)
1727
# Start GnuTLS connection
1036
1729
session.handshake()
1037
except gnutls.errors.GNUTLSError, error:
1038
logger.warning(u"Handshake failed: %s", error)
1730
except gnutls.errors.GNUTLSError as error:
1731
logger.warning("Handshake failed: %s", error)
1039
1732
# Do not run session.bye() here: the session is not
1040
1733
# established. Just abandon the request.
1042
logger.debug(u"Handshake succeeded")
1735
logger.debug("Handshake succeeded")
1737
approval_required = False
1045
fpr = self.fingerprint(self.peer_certificate
1047
except (TypeError, gnutls.errors.GNUTLSError), error:
1048
logger.warning(u"Bad certificate: %s", error)
1050
logger.debug(u"Fingerprint: %s", fpr)
1052
for c in self.server.clients:
1053
if c.fingerprint == fpr:
1740
fpr = self.fingerprint(
1741
self.peer_certificate(session))
1743
gnutls.errors.GNUTLSError) as error:
1744
logger.warning("Bad certificate: %s", error)
1746
logger.debug("Fingerprint: %s", fpr)
1749
client = ProxyClient(child_pipe, fpr,
1750
self.client_address)
1754
if client.approval_delay:
1755
delay = client.approval_delay
1756
client.approvals_pending += 1
1757
approval_required = True
1760
if not client.enabled:
1761
logger.info("Client %s is disabled",
1763
if self.server.use_dbus:
1765
client.Rejected("Disabled")
1768
if client.approved or not client.approval_delay:
1769
#We are approved or approval is disabled
1057
ipc.write(u"NOTFOUND %s %s\n"
1058
% (fpr, unicode(self.client_address)))
1060
# Have to check if client.enabled, since it is
1061
# possible that the client was disabled since the
1062
# GnuTLS session was established.
1063
ipc.write(u"GETATTR enabled %s\n" % fpr)
1064
enabled = pickle.load(ipc_return)
1066
ipc.write(u"DISABLED %s\n" % client.name)
1068
# Send "NEED_APPROVAL" here and hang waiting
1069
# for response? Leave timeout to parent process?
1070
ipc.write(u"SENDING %s\n" % client.name)
1771
elif client.approved is None:
1772
logger.info("Client %s needs approval",
1774
if self.server.use_dbus:
1776
client.NeedApproval(
1777
client.approval_delay.total_seconds()
1778
* 1000, client.approved_by_default)
1780
logger.warning("Client %s was not approved",
1782
if self.server.use_dbus:
1784
client.Rejected("Denied")
1787
#wait until timeout or approved
1788
time = datetime.datetime.now()
1789
client.changedstate.acquire()
1790
client.changedstate.wait(delay.total_seconds())
1791
client.changedstate.release()
1792
time2 = datetime.datetime.now()
1793
if (time2 - time) >= delay:
1794
if not client.approved_by_default:
1795
logger.warning("Client %s timed out while"
1796
" waiting for approval",
1798
if self.server.use_dbus:
1800
client.Rejected("Approval timed out")
1805
delay -= time2 - time
1072
1808
while sent_size < len(client.secret):
1073
sent = session.send(client.secret[sent_size:])
1074
logger.debug(u"Sent: %d, remaining: %d",
1075
sent, len(client.secret)
1076
- (sent_size + sent))
1810
sent = session.send(client.secret[sent_size:])
1811
except gnutls.errors.GNUTLSError as error:
1812
logger.warning("gnutls send failed",
1815
logger.debug("Sent: %d, remaining: %d", sent,
1816
len(client.secret) - (sent_size
1077
1818
sent_size += sent
1820
logger.info("Sending secret to %s", client.name)
1821
# bump the timeout using extended_timeout
1822
client.bump_timeout(client.extended_timeout)
1823
if self.server.use_dbus:
1828
if approval_required:
1829
client.approvals_pending -= 1
1832
except gnutls.errors.GNUTLSError as error:
1833
logger.warning("GnuTLS bye failed",
1082
1837
def peer_certificate(session):
1083
1838
"Return the peer's OpenPGP certificate as a bytestring"
1084
1839
# If not an OpenPGP certificate...
1085
if (gnutls.library.functions
1086
.gnutls_certificate_type_get(session._c_object)
1840
if (gnutls.library.functions.gnutls_certificate_type_get(
1087
1842
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1088
1843
# ...do the normal thing
1089
1844
return session.peer_certificate
1103
1858
def fingerprint(openpgp):
1104
1859
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1105
1860
# New GnuTLS "datum" with the OpenPGP public key
1106
datum = (gnutls.library.types
1107
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1110
ctypes.c_uint(len(openpgp))))
1861
datum = gnutls.library.types.gnutls_datum_t(
1862
ctypes.cast(ctypes.c_char_p(openpgp),
1863
ctypes.POINTER(ctypes.c_ubyte)),
1864
ctypes.c_uint(len(openpgp)))
1111
1865
# New empty GnuTLS certificate
1112
1866
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1113
(gnutls.library.functions
1114
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1867
gnutls.library.functions.gnutls_openpgp_crt_init(
1115
1869
# Import the OpenPGP public key into the certificate
1116
(gnutls.library.functions
1117
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1118
gnutls.library.constants
1119
.GNUTLS_OPENPGP_FMT_RAW))
1870
gnutls.library.functions.gnutls_openpgp_crt_import(
1871
crt, ctypes.byref(datum),
1872
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1120
1873
# Verify the self signature in the key
1121
1874
crtverify = ctypes.c_uint()
1122
(gnutls.library.functions
1123
.gnutls_openpgp_crt_verify_self(crt, 0,
1124
ctypes.byref(crtverify)))
1875
gnutls.library.functions.gnutls_openpgp_crt_verify_self(
1876
crt, 0, ctypes.byref(crtverify))
1125
1877
if crtverify.value != 0:
1126
1878
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1127
raise (gnutls.errors.CertificateSecurityError
1879
raise gnutls.errors.CertificateSecurityError(
1129
1881
# New buffer for the fingerprint
1130
1882
buf = ctypes.create_string_buffer(20)
1131
1883
buf_len = ctypes.c_size_t()
1132
1884
# Get the fingerprint from the certificate into the buffer
1133
(gnutls.library.functions
1134
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1135
ctypes.byref(buf_len)))
1885
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
1886
crt, ctypes.byref(buf), ctypes.byref(buf_len))
1136
1887
# Deinit the certificate
1137
1888
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1138
1889
# Convert the buffer to a Python bytestring
1139
1890
fpr = ctypes.string_at(buf, buf_len.value)
1140
1891
# Convert the bytestring to hexadecimal notation
1141
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1892
hex_fpr = binascii.hexlify(fpr).upper()
1145
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1146
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1896
class MultiprocessingMixIn(object):
1897
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1899
def sub_process_main(self, request, address):
1901
self.finish_request(request, address)
1903
self.handle_error(request, address)
1904
self.close_request(request)
1906
def process_request(self, request, address):
1907
"""Start a new process to process the request."""
1908
proc = multiprocessing.Process(target = self.sub_process_main,
1909
args = (request, address))
1914
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1915
""" adds a pipe to the MixIn """
1147
1917
def process_request(self, request, client_address):
1148
1918
"""Overrides and wraps the original process_request().
1150
1920
This function creates a new pipe in self.pipe
1152
# Child writes to child_pipe
1153
self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1154
# Parent writes to parent_pipe
1155
self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1156
super(ForkingMixInWithPipes,
1157
self).process_request(request, client_address)
1158
# Close unused ends for parent
1159
self.parent_pipe[0].close() # close read end
1160
self.child_pipe[1].close() # close write end
1161
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1162
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1922
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1924
proc = MultiprocessingMixIn.process_request(self, request,
1926
self.child_pipe.close()
1927
self.add_pipe(parent_pipe, proc)
1929
def add_pipe(self, parent_pipe, proc):
1163
1930
"""Dummy function; override as necessary"""
1164
child_pipe_fd.close()
1165
parent_pipe_fd.close()
1168
class IPv6_TCPServer(ForkingMixInWithPipes,
1931
raise NotImplementedError()
1934
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1169
1935
socketserver.TCPServer, object):
1170
1936
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
1239
2038
Assumes a gobject.MainLoop event loop.
1241
2041
def __init__(self, server_address, RequestHandlerClass,
1242
interface=None, use_ipv6=True, clients=None,
1243
gnutls_priority=None, use_dbus=True):
2045
gnutls_priority=None,
1244
2048
self.enabled = False
1245
2049
self.clients = clients
1246
2050
if self.clients is None:
1247
self.clients = set()
1248
2052
self.use_dbus = use_dbus
1249
2053
self.gnutls_priority = gnutls_priority
1250
2054
IPv6_TCPServer.__init__(self, server_address,
1251
2055
RequestHandlerClass,
1252
2056
interface = interface,
1253
use_ipv6 = use_ipv6)
2057
use_ipv6 = use_ipv6,
2058
socketfd = socketfd)
1254
2060
def server_activate(self):
1255
2061
if self.enabled:
1256
2062
return socketserver.TCPServer.server_activate(self)
1257
2064
def enable(self):
1258
2065
self.enabled = True
1259
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
2067
def add_pipe(self, parent_pipe, proc):
1260
2068
# Call "handle_ipc" for both data and EOF events
1261
gobject.io_add_watch(child_pipe_fd.fileno(),
1262
gobject.IO_IN | gobject.IO_HUP,
1263
functools.partial(self.handle_ipc,
1264
reply = parent_pipe_fd,
1265
sender= child_pipe_fd))
1266
def handle_ipc(self, source, condition, reply=None, sender=None):
1268
gobject.IO_IN: u"IN", # There is data to read.
1269
gobject.IO_OUT: u"OUT", # Data can be written (without
1271
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1272
gobject.IO_ERR: u"ERR", # Error condition.
1273
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1274
# broken, usually for pipes and
1277
conditions_string = ' | '.join(name
1279
condition_names.iteritems()
1280
if cond & condition)
1281
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1284
# Read a line from the file object
1285
cmdline = sender.readline()
1286
if not cmdline: # Empty line means end of file
1287
# close the IPC pipes
1291
# Stop calling this function
1294
logger.debug(u"IPC command: %r", cmdline)
1296
# Parse and act on command
1297
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1299
if cmd == u"NOTFOUND":
1300
fpr, address = args.split(None, 1)
1301
logger.warning(u"Client not found for fingerprint: %s, ad"
1302
u"dress: %s", fpr, address)
1305
mandos_dbus_service.ClientNotFound(fpr, address)
1306
elif cmd == u"DISABLED":
1307
for client in self.clients:
1308
if client.name == args:
1309
logger.warning(u"Client %s is disabled", args)
1315
logger.error(u"Unknown client %s is disabled", args)
1316
elif cmd == u"SENDING":
1317
for client in self.clients:
1318
if client.name == args:
1319
logger.info(u"Sending secret to %s", client.name)
1326
logger.error(u"Sending secret to unknown client %s",
1328
elif cmd == u"GETATTR":
1329
attr_name, fpr = args.split(None, 1)
1330
for client in self.clients:
1331
if client.fingerprint == fpr:
1332
attr_value = getattr(client, attr_name, None)
1333
logger.debug("IPC reply: %r", attr_value)
1334
pickle.dump(attr_value, reply)
1337
logger.error(u"Client %s on address %s requesting "
1338
u"attribute %s not found", fpr, address,
1340
pickle.dump(None, reply)
2069
gobject.io_add_watch(
2070
parent_pipe.fileno(),
2071
gobject.IO_IN | gobject.IO_HUP,
2072
functools.partial(self.handle_ipc,
2073
parent_pipe = parent_pipe,
2076
def handle_ipc(self, source, condition,
2079
client_object=None):
2080
# error, or the other end of multiprocessing.Pipe has closed
2081
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2082
# Wait for other process to exit
2086
# Read a request from the child
2087
request = parent_pipe.recv()
2088
command = request[0]
2090
if command == 'init':
2092
address = request[2]
2094
for c in self.clients.itervalues():
2095
if c.fingerprint == fpr:
2099
logger.info("Client not found for fingerprint: %s, ad"
2100
"dress: %s", fpr, address)
2103
mandos_dbus_service.ClientNotFound(fpr,
2105
parent_pipe.send(False)
2108
gobject.io_add_watch(
2109
parent_pipe.fileno(),
2110
gobject.IO_IN | gobject.IO_HUP,
2111
functools.partial(self.handle_ipc,
2112
parent_pipe = parent_pipe,
2114
client_object = client))
2115
parent_pipe.send(True)
2116
# remove the old hook in favor of the new above hook on
2119
if command == 'funcall':
2120
funcname = request[1]
2124
parent_pipe.send(('data', getattr(client_object,
2128
if command == 'getattr':
2129
attrname = request[1]
2130
if callable(client_object.__getattribute__(attrname)):
2131
parent_pipe.send(('function', ))
2134
'data', client_object.__getattribute__(attrname)))
2136
if command == 'setattr':
2137
attrname = request[1]
2139
setattr(client_object, attrname, value)
2144
def rfc3339_duration_to_delta(duration):
2145
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2147
>>> rfc3339_duration_to_delta("P7D")
2148
datetime.timedelta(7)
2149
>>> rfc3339_duration_to_delta("PT60S")
2150
datetime.timedelta(0, 60)
2151
>>> rfc3339_duration_to_delta("PT60M")
2152
datetime.timedelta(0, 3600)
2153
>>> rfc3339_duration_to_delta("PT24H")
2154
datetime.timedelta(1)
2155
>>> rfc3339_duration_to_delta("P1W")
2156
datetime.timedelta(7)
2157
>>> rfc3339_duration_to_delta("PT5M30S")
2158
datetime.timedelta(0, 330)
2159
>>> rfc3339_duration_to_delta("P1DT3M20S")
2160
datetime.timedelta(1, 200)
2163
# Parsing an RFC 3339 duration with regular expressions is not
2164
# possible - there would have to be multiple places for the same
2165
# values, like seconds. The current code, while more esoteric, is
2166
# cleaner without depending on a parsing library. If Python had a
2167
# built-in library for parsing we would use it, but we'd like to
2168
# avoid excessive use of external libraries.
2170
# New type for defining tokens, syntax, and semantics all-in-one
2171
Token = collections.namedtuple("Token",
2172
("regexp", # To match token; if
2173
# "value" is not None,
2174
# must have a "group"
2176
"value", # datetime.timedelta or
2178
"followers")) # Tokens valid after
2180
Token = collections.namedtuple("Token", (
2181
"regexp", # To match token; if "value" is not None, must have
2182
# a "group" containing digits
2183
"value", # datetime.timedelta or None
2184
"followers")) # Tokens valid after this token
2185
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2186
# the "duration" ABNF definition in RFC 3339, Appendix A.
2187
token_end = Token(re.compile(r"$"), None, frozenset())
2188
token_second = Token(re.compile(r"(\d+)S"),
2189
datetime.timedelta(seconds=1),
2190
frozenset((token_end, )))
2191
token_minute = Token(re.compile(r"(\d+)M"),
2192
datetime.timedelta(minutes=1),
2193
frozenset((token_second, token_end)))
2194
token_hour = Token(re.compile(r"(\d+)H"),
2195
datetime.timedelta(hours=1),
2196
frozenset((token_minute, token_end)))
2197
token_time = Token(re.compile(r"T"),
2199
frozenset((token_hour, token_minute,
2201
token_day = Token(re.compile(r"(\d+)D"),
2202
datetime.timedelta(days=1),
2203
frozenset((token_time, token_end)))
2204
token_month = Token(re.compile(r"(\d+)M"),
2205
datetime.timedelta(weeks=4),
2206
frozenset((token_day, token_end)))
2207
token_year = Token(re.compile(r"(\d+)Y"),
2208
datetime.timedelta(weeks=52),
2209
frozenset((token_month, token_end)))
2210
token_week = Token(re.compile(r"(\d+)W"),
2211
datetime.timedelta(weeks=1),
2212
frozenset((token_end, )))
2213
token_duration = Token(re.compile(r"P"), None,
2214
frozenset((token_year, token_month,
2215
token_day, token_time,
2217
# Define starting values
2218
value = datetime.timedelta() # Value so far
2220
followers = frozenset((token_duration,)) # Following valid tokens
2221
s = duration # String left to parse
2222
# Loop until end token is found
2223
while found_token is not token_end:
2224
# Search for any currently valid tokens
2225
for token in followers:
2226
match = token.regexp.match(s)
2227
if match is not None:
2229
if token.value is not None:
2230
# Value found, parse digits
2231
factor = int(match.group(1), 10)
2232
# Add to value so far
2233
value += factor * token.value
2234
# Strip token from string
2235
s = token.regexp.sub("", s, 1)
2238
# Set valid next tokens
2239
followers = found_token.followers
1342
logger.error(u"Unknown IPC command: %r", cmdline)
1344
# Keep calling this function
2242
# No currently valid tokens were found
2243
raise ValueError("Invalid RFC 3339 duration")
1348
2248
def string_to_delta(interval):
1349
2249
"""Parse a string and return a datetime.timedelta
1351
>>> string_to_delta(u'7d')
2251
>>> string_to_delta('7d')
1352
2252
datetime.timedelta(7)
1353
>>> string_to_delta(u'60s')
2253
>>> string_to_delta('60s')
1354
2254
datetime.timedelta(0, 60)
1355
>>> string_to_delta(u'60m')
2255
>>> string_to_delta('60m')
1356
2256
datetime.timedelta(0, 3600)
1357
>>> string_to_delta(u'24h')
2257
>>> string_to_delta('24h')
1358
2258
datetime.timedelta(1)
1359
>>> string_to_delta(u'1w')
2259
>>> string_to_delta('1w')
1360
2260
datetime.timedelta(7)
1361
>>> string_to_delta(u'5m 30s')
2261
>>> string_to_delta('5m 30s')
1362
2262
datetime.timedelta(0, 330)
2266
return rfc3339_duration_to_delta(interval)
1364
2270
timevalue = datetime.timedelta(0)
1365
2271
for s in interval.split():
1367
suffix = unicode(s[-1])
1368
2274
value = int(s[:-1])
1370
2276
delta = datetime.timedelta(value)
1371
elif suffix == u"s":
1372
2278
delta = datetime.timedelta(0, value)
1373
elif suffix == u"m":
1374
2280
delta = datetime.timedelta(0, 0, 0, 0, value)
1375
elif suffix == u"h":
1376
2282
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1377
elif suffix == u"w":
1378
2284
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1380
raise ValueError(u"Unknown suffix %r" % suffix)
1381
except (ValueError, IndexError), e:
1382
raise ValueError(e.message)
2286
raise ValueError("Unknown suffix {!r}".format(suffix))
2287
except IndexError as e:
2288
raise ValueError(*(e.args))
1383
2289
timevalue += delta
1384
2290
return timevalue
1387
def if_nametoindex(interface):
1388
"""Call the C function if_nametoindex(), or equivalent
1390
Note: This function cannot accept a unicode string."""
1391
global if_nametoindex
1393
if_nametoindex = (ctypes.cdll.LoadLibrary
1394
(ctypes.util.find_library(u"c"))
1396
except (OSError, AttributeError):
1397
logger.warning(u"Doing if_nametoindex the hard way")
1398
def if_nametoindex(interface):
1399
"Get an interface index the hard way, i.e. using fcntl()"
1400
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1401
with contextlib.closing(socket.socket()) as s:
1402
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1403
struct.pack(str(u"16s16x"),
1405
interface_index = struct.unpack(str(u"I"),
1407
return interface_index
1408
return if_nametoindex(interface)
1411
2293
def daemon(nochdir = False, noclose = False):
1412
2294
"""See daemon(3). Standard BSD Unix function.
1438
2320
##################################################################
1439
2321
# Parsing of options, both command line and config file
1441
parser = optparse.OptionParser(version = "%%prog %s" % version)
1442
parser.add_option("-i", u"--interface", type=u"string",
1443
metavar="IF", help=u"Bind to interface IF")
1444
parser.add_option("-a", u"--address", type=u"string",
1445
help=u"Address to listen for requests on")
1446
parser.add_option("-p", u"--port", type=u"int",
1447
help=u"Port number to receive requests on")
1448
parser.add_option("--check", action=u"store_true",
1449
help=u"Run self-test")
1450
parser.add_option("--debug", action=u"store_true",
1451
help=u"Debug mode; run in foreground and log to"
1453
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1454
u" priority string (see GnuTLS documentation)")
1455
parser.add_option("--servicename", type=u"string",
1456
metavar=u"NAME", help=u"Zeroconf service name")
1457
parser.add_option("--configdir", type=u"string",
1458
default=u"/etc/mandos", metavar=u"DIR",
1459
help=u"Directory to search for configuration"
1461
parser.add_option("--no-dbus", action=u"store_false",
1462
dest=u"use_dbus", help=u"Do not provide D-Bus"
1463
u" system bus interface")
1464
parser.add_option("--no-ipv6", action=u"store_false",
1465
dest=u"use_ipv6", help=u"Do not use IPv6")
1466
options = parser.parse_args()[0]
2323
parser = argparse.ArgumentParser()
2324
parser.add_argument("-v", "--version", action="version",
2325
version = "%(prog)s {}".format(version),
2326
help="show version number and exit")
2327
parser.add_argument("-i", "--interface", metavar="IF",
2328
help="Bind to interface IF")
2329
parser.add_argument("-a", "--address",
2330
help="Address to listen for requests on")
2331
parser.add_argument("-p", "--port", type=int,
2332
help="Port number to receive requests on")
2333
parser.add_argument("--check", action="store_true",
2334
help="Run self-test")
2335
parser.add_argument("--debug", action="store_true",
2336
help="Debug mode; run in foreground and log"
2337
" to terminal", default=None)
2338
parser.add_argument("--debuglevel", metavar="LEVEL",
2339
help="Debug level for stdout output")
2340
parser.add_argument("--priority", help="GnuTLS"
2341
" priority string (see GnuTLS documentation)")
2342
parser.add_argument("--servicename",
2343
metavar="NAME", help="Zeroconf service name")
2344
parser.add_argument("--configdir",
2345
default="/etc/mandos", metavar="DIR",
2346
help="Directory to search for configuration"
2348
parser.add_argument("--no-dbus", action="store_false",
2349
dest="use_dbus", help="Do not provide D-Bus"
2350
" system bus interface", default=None)
2351
parser.add_argument("--no-ipv6", action="store_false",
2352
dest="use_ipv6", help="Do not use IPv6",
2354
parser.add_argument("--no-restore", action="store_false",
2355
dest="restore", help="Do not restore stored"
2356
" state", default=None)
2357
parser.add_argument("--socket", type=int,
2358
help="Specify a file descriptor to a network"
2359
" socket to use instead of creating one")
2360
parser.add_argument("--statedir", metavar="DIR",
2361
help="Directory to save/restore state in")
2362
parser.add_argument("--foreground", action="store_true",
2363
help="Run in foreground", default=None)
2364
parser.add_argument("--no-zeroconf", action="store_false",
2365
dest="zeroconf", help="Do not use Zeroconf",
2368
options = parser.parse_args()
1468
2370
if options.check:
2372
fail_count, test_count = doctest.testmod()
2373
sys.exit(os.EX_OK if fail_count == 0 else 1)
1473
2375
# Default values for config file for server-global settings
1474
server_defaults = { u"interface": u"",
1479
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1480
u"servicename": u"Mandos",
1481
u"use_dbus": u"True",
1482
u"use_ipv6": u"True",
2376
server_defaults = { "interface": "",
2381
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2382
":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2383
"servicename": "Mandos",
2389
"statedir": "/var/lib/mandos",
2390
"foreground": "False",
1485
2394
# Parse config file for server-global settings
1486
2395
server_config = configparser.SafeConfigParser(server_defaults)
1487
2396
del server_defaults
1488
server_config.read(os.path.join(options.configdir,
2397
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1490
2398
# Convert the SafeConfigParser object to a dict
1491
2399
server_settings = server_config.defaults()
1492
2400
# Use the appropriate methods on the non-string config options
1493
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1494
server_settings[option] = server_config.getboolean(u"DEFAULT",
2401
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2402
server_settings[option] = server_config.getboolean("DEFAULT",
1496
2404
if server_settings["port"]:
1497
server_settings["port"] = server_config.getint(u"DEFAULT",
2405
server_settings["port"] = server_config.getint("DEFAULT",
2407
if server_settings["socket"]:
2408
server_settings["socket"] = server_config.getint("DEFAULT",
2410
# Later, stdin will, and stdout and stderr might, be dup'ed
2411
# over with an opened os.devnull. But we don't want this to
2412
# happen with a supplied network socket.
2413
if 0 <= server_settings["socket"] <= 2:
2414
server_settings["socket"] = os.dup(server_settings
1499
2416
del server_config
1501
2418
# Override the settings from the config file with command line
1502
2419
# options, if set.
1503
for option in (u"interface", u"address", u"port", u"debug",
1504
u"priority", u"servicename", u"configdir",
1505
u"use_dbus", u"use_ipv6"):
2420
for option in ("interface", "address", "port", "debug",
2421
"priority", "servicename", "configdir", "use_dbus",
2422
"use_ipv6", "debuglevel", "restore", "statedir",
2423
"socket", "foreground", "zeroconf"):
1506
2424
value = getattr(options, option)
1507
2425
if value is not None:
1508
2426
server_settings[option] = value
1510
2428
# Force all strings to be unicode
1511
2429
for option in server_settings.keys():
1512
if type(server_settings[option]) is str:
1513
server_settings[option] = unicode(server_settings[option])
2430
if isinstance(server_settings[option], bytes):
2431
server_settings[option] = (server_settings[option]
2433
# Force all boolean options to be boolean
2434
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2435
"foreground", "zeroconf"):
2436
server_settings[option] = bool(server_settings[option])
2437
# Debug implies foreground
2438
if server_settings["debug"]:
2439
server_settings["foreground"] = True
1514
2440
# Now we have our good server settings in "server_settings"
1516
2442
##################################################################
2444
if (not server_settings["zeroconf"]
2445
and not (server_settings["port"]
2446
or server_settings["socket"] != "")):
2447
parser.error("Needs port or socket to work without Zeroconf")
1518
2449
# For convenience
1519
debug = server_settings[u"debug"]
1520
use_dbus = server_settings[u"use_dbus"]
1521
use_ipv6 = server_settings[u"use_ipv6"]
1524
syslogger.setLevel(logging.WARNING)
1525
console.setLevel(logging.WARNING)
1527
if server_settings[u"servicename"] != u"Mandos":
1528
syslogger.setFormatter(logging.Formatter
1529
(u'Mandos (%s) [%%(process)d]:'
1530
u' %%(levelname)s: %%(message)s'
1531
% server_settings[u"servicename"]))
2450
debug = server_settings["debug"]
2451
debuglevel = server_settings["debuglevel"]
2452
use_dbus = server_settings["use_dbus"]
2453
use_ipv6 = server_settings["use_ipv6"]
2454
stored_state_path = os.path.join(server_settings["statedir"],
2456
foreground = server_settings["foreground"]
2457
zeroconf = server_settings["zeroconf"]
2460
initlogger(debug, logging.DEBUG)
2465
level = getattr(logging, debuglevel.upper())
2466
initlogger(debug, level)
2468
if server_settings["servicename"] != "Mandos":
2469
syslogger.setFormatter(
2470
logging.Formatter('Mandos ({}) [%(process)d]:'
2471
' %(levelname)s: %(message)s'.format(
2472
server_settings["servicename"])))
1533
2474
# Parse config file with clients
1534
client_defaults = { u"timeout": u"1h",
1536
u"checker": u"fping -q -- %%(host)s",
1539
client_config = configparser.SafeConfigParser(client_defaults)
1540
client_config.read(os.path.join(server_settings[u"configdir"],
2475
client_config = configparser.SafeConfigParser(Client
2477
client_config.read(os.path.join(server_settings["configdir"],
1543
2480
global mandos_dbus_service
1544
2481
mandos_dbus_service = None
1546
tcp_server = MandosServer((server_settings[u"address"],
1547
server_settings[u"port"]),
1549
interface=server_settings[u"interface"],
1552
server_settings[u"priority"],
1554
pidfilename = u"/var/run/mandos.pid"
1556
pidfile = open(pidfilename, u"w")
1558
logger.error(u"Could not open file %r", pidfilename)
2484
if server_settings["socket"] != "":
2485
socketfd = server_settings["socket"]
2486
tcp_server = MandosServer(
2487
(server_settings["address"], server_settings["port"]),
2489
interface=(server_settings["interface"] or None),
2491
gnutls_priority=server_settings["priority"],
2495
pidfilename = "/run/mandos.pid"
2496
if not os.path.isdir("/run/."):
2497
pidfilename = "/var/run/mandos.pid"
2500
pidfile = open(pidfilename, "w")
2501
except IOError as e:
2502
logger.error("Could not open file %r", pidfilename,
1561
uid = pwd.getpwnam(u"_mandos").pw_uid
1562
gid = pwd.getpwnam(u"_mandos").pw_gid
2505
for name in ("_mandos", "mandos", "nobody"):
1565
uid = pwd.getpwnam(u"mandos").pw_uid
1566
gid = pwd.getpwnam(u"mandos").pw_gid
2507
uid = pwd.getpwnam(name).pw_uid
2508
gid = pwd.getpwnam(name).pw_gid
1567
2510
except KeyError:
1569
uid = pwd.getpwnam(u"nobody").pw_uid
1570
gid = pwd.getpwnam(u"nobody").pw_gid
1577
except OSError, error:
1578
if error[0] != errno.EPERM:
2518
except OSError as error:
2519
if error.errno != errno.EPERM:
1581
# Enable all possible GnuTLS debugging
2523
# Enable all possible GnuTLS debugging
1583
2525
# "Use a log level over 10 to enable all debugging options."
1584
2526
# - GnuTLS manual
1585
2527
gnutls.library.functions.gnutls_global_set_log_level(11)
1587
2529
@gnutls.library.types.gnutls_log_func
1588
2530
def debug_gnutls(level, string):
1589
logger.debug(u"GnuTLS: %s", string[:-1])
1591
(gnutls.library.functions
1592
.gnutls_global_set_log_function(debug_gnutls))
2531
logger.debug("GnuTLS: %s", string[:-1])
2533
gnutls.library.functions.gnutls_global_set_log_function(
2536
# Redirect stdin so all checkers get /dev/null
2537
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2538
os.dup2(null, sys.stdin.fileno())
2542
# Need to fork before connecting to D-Bus
2544
# Close all input and output, do double fork, etc.
2547
# multiprocessing will use threads, so before we use gobject we
2548
# need to inform gobject that threads will be used.
2549
gobject.threads_init()
1594
2551
global main_loop
1595
2552
# From the Avahi example code
1596
DBusGMainLoop(set_as_default=True )
2553
DBusGMainLoop(set_as_default=True)
1597
2554
main_loop = gobject.MainLoop()
1598
2555
bus = dbus.SystemBus()
1599
2556
# End of Avahi example code
1602
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1603
bus, do_not_queue=True)
1604
except dbus.exceptions.NameExistsException, e:
1605
logger.error(unicode(e) + u", disabling D-Bus")
2559
bus_name = dbus.service.BusName("se.recompile.Mandos",
2562
old_bus_name = dbus.service.BusName(
2563
"se.bsnet.fukt.Mandos", bus,
2565
except dbus.exceptions.NameExistsException as e:
2566
logger.error("Disabling D-Bus:", exc_info=e)
1606
2567
use_dbus = False
1607
server_settings[u"use_dbus"] = False
2568
server_settings["use_dbus"] = False
1608
2569
tcp_server.use_dbus = False
1609
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1610
service = AvahiService(name = server_settings[u"servicename"],
1611
servicetype = u"_mandos._tcp",
1612
protocol = protocol, bus = bus)
1613
if server_settings["interface"]:
1614
service.interface = (if_nametoindex
1615
(str(server_settings[u"interface"])))
2571
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2572
service = AvahiServiceToSyslog(
2573
name = server_settings["servicename"],
2574
servicetype = "_mandos._tcp",
2575
protocol = protocol,
2577
if server_settings["interface"]:
2578
service.interface = if_nametoindex(
2579
server_settings["interface"].encode("utf-8"))
2581
global multiprocessing_manager
2582
multiprocessing_manager = multiprocessing.Manager()
1617
2584
client_class = Client
1619
2586
client_class = functools.partial(ClientDBus, bus = bus)
1620
tcp_server.clients.update(set(
1621
client_class(name = section,
1622
config= dict(client_config.items(section)))
1623
for section in client_config.sections()))
2588
client_settings = Client.config_parser(client_config)
2589
old_client_settings = {}
2592
# This is used to redirect stdout and stderr for checker processes
2594
wnull = open(os.devnull, "w") # A writable /dev/null
2595
# Only used if server is running in foreground but not in debug
2597
if debug or not foreground:
2600
# Get client data and settings from last running state.
2601
if server_settings["restore"]:
2603
with open(stored_state_path, "rb") as stored_state:
2604
clients_data, old_client_settings = pickle.load(
2606
os.remove(stored_state_path)
2607
except IOError as e:
2608
if e.errno == errno.ENOENT:
2609
logger.warning("Could not load persistent state:"
2610
" {}".format(os.strerror(e.errno)))
2612
logger.critical("Could not load persistent state:",
2615
except EOFError as e:
2616
logger.warning("Could not load persistent state: "
2620
with PGPEngine() as pgp:
2621
for client_name, client in clients_data.items():
2622
# Skip removed clients
2623
if client_name not in client_settings:
2626
# Decide which value to use after restoring saved state.
2627
# We have three different values: Old config file,
2628
# new config file, and saved state.
2629
# New config value takes precedence if it differs from old
2630
# config value, otherwise use saved state.
2631
for name, value in client_settings[client_name].items():
2633
# For each value in new config, check if it
2634
# differs from the old config value (Except for
2635
# the "secret" attribute)
2636
if (name != "secret"
2638
old_client_settings[client_name][name])):
2639
client[name] = value
2643
# Clients who has passed its expire date can still be
2644
# enabled if its last checker was successful. A Client
2645
# whose checker succeeded before we stored its state is
2646
# assumed to have successfully run all checkers during
2648
if client["enabled"]:
2649
if datetime.datetime.utcnow() >= client["expires"]:
2650
if not client["last_checked_ok"]:
2652
"disabling client {} - Client never "
2653
"performed a successful checker".format(
2655
client["enabled"] = False
2656
elif client["last_checker_status"] != 0:
2658
"disabling client {} - Client last"
2659
" checker failed with error code"
2662
client["last_checker_status"]))
2663
client["enabled"] = False
2665
client["expires"] = (
2666
datetime.datetime.utcnow()
2667
+ client["timeout"])
2668
logger.debug("Last checker succeeded,"
2669
" keeping {} enabled".format(
2672
client["secret"] = pgp.decrypt(
2673
client["encrypted_secret"],
2674
client_settings[client_name]["secret"])
2676
# If decryption fails, we use secret from new settings
2677
logger.debug("Failed to decrypt {} old secret".format(
2679
client["secret"] = (client_settings[client_name]
2682
# Add/remove clients based on new changes made to config
2683
for client_name in (set(old_client_settings)
2684
- set(client_settings)):
2685
del clients_data[client_name]
2686
for client_name in (set(client_settings)
2687
- set(old_client_settings)):
2688
clients_data[client_name] = client_settings[client_name]
2690
# Create all client objects
2691
for client_name, client in clients_data.items():
2692
tcp_server.clients[client_name] = client_class(
2695
server_settings = server_settings)
1624
2697
if not tcp_server.clients:
1625
logger.warning(u"No clients defined")
1628
# Redirect stdin so all checkers get /dev/null
1629
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1630
os.dup2(null, sys.stdin.fileno())
1634
# No console logging
1635
logger.removeHandler(console)
1636
# Close all input and output, do double fork, etc.
1642
pidfile.write(str(pid) + "\n")
2698
logger.warning("No clients defined")
2701
if pidfile is not None:
2705
pidfile.write("{}\n".format(pid).encode("utf-8"))
2707
logger.error("Could not write to file %r with PID %d",
1645
logger.error(u"Could not write to file %r with PID %d",
1648
# "pidfile" was never created
1653
signal.signal(signal.SIGINT, signal.SIG_IGN)
1654
2712
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1655
2713
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1658
class MandosDBusService(dbus.service.Object):
2717
@alternate_dbus_interfaces(
2718
{ "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2719
class MandosDBusService(DBusObjectWithProperties):
1659
2720
"""A D-Bus proxy object"""
1660
2722
def __init__(self):
1661
dbus.service.Object.__init__(self, bus, u"/")
1662
_interface = u"se.bsnet.fukt.Mandos"
1664
@dbus.service.signal(_interface, signature=u"o")
2723
dbus.service.Object.__init__(self, bus, "/")
2725
_interface = "se.recompile.Mandos"
2727
@dbus_interface_annotations(_interface)
2730
"org.freedesktop.DBus.Property.EmitsChangedSignal":
2733
@dbus.service.signal(_interface, signature="o")
1665
2734
def ClientAdded(self, objpath):
1669
@dbus.service.signal(_interface, signature=u"ss")
2738
@dbus.service.signal(_interface, signature="ss")
1670
2739
def ClientNotFound(self, fingerprint, address):
1674
@dbus.service.signal(_interface, signature=u"os")
2743
@dbus.service.signal(_interface, signature="os")
1675
2744
def ClientRemoved(self, objpath, name):
1679
@dbus.service.method(_interface, out_signature=u"ao")
2748
@dbus.service.method(_interface, out_signature="ao")
1680
2749
def GetAllClients(self):
1682
return dbus.Array(c.dbus_object_path
1683
for c in tcp_server.clients)
2751
return dbus.Array(c.dbus_object_path for c in
2752
tcp_server.clients.itervalues())
1685
2754
@dbus.service.method(_interface,
1686
out_signature=u"a{oa{sv}}")
2755
out_signature="a{oa{sv}}")
1687
2756
def GetAllClientsWithProperties(self):
1689
2758
return dbus.Dictionary(
1690
((c.dbus_object_path, c.GetAll(u""))
1691
for c in tcp_server.clients),
1692
signature=u"oa{sv}")
2759
{ c.dbus_object_path: c.GetAll("")
2760
for c in tcp_server.clients.itervalues() },
1694
@dbus.service.method(_interface, in_signature=u"o")
2763
@dbus.service.method(_interface, in_signature="o")
1695
2764
def RemoveClient(self, object_path):
1697
for c in tcp_server.clients:
2766
for c in tcp_server.clients.itervalues():
1698
2767
if c.dbus_object_path == object_path:
1699
tcp_server.clients.remove(c)
2768
del tcp_server.clients[c.name]
1700
2769
c.remove_from_connection()
1701
2770
# Don't signal anything except ClientRemoved
1702
2771
c.disable(quiet=True)
1713
2782
"Cleanup function; run on exit"
2786
multiprocessing.active_children()
2788
if not (tcp_server.clients or client_settings):
2791
# Store client before exiting. Secrets are encrypted with key
2792
# based on what config file has. If config file is
2793
# removed/edited, old secret will thus be unrecovable.
2795
with PGPEngine() as pgp:
2796
for client in tcp_server.clients.itervalues():
2797
key = client_settings[client.name]["secret"]
2798
client.encrypted_secret = pgp.encrypt(client.secret,
2802
# A list of attributes that can not be pickled
2804
exclude = { "bus", "changedstate", "secret",
2805
"checker", "server_settings" }
2806
for name, typ in inspect.getmembers(dbus.service
2810
client_dict["encrypted_secret"] = (client
2812
for attr in client.client_structure:
2813
if attr not in exclude:
2814
client_dict[attr] = getattr(client, attr)
2816
clients[client.name] = client_dict
2817
del client_settings[client.name]["secret"]
2820
with tempfile.NamedTemporaryFile(
2824
dir=os.path.dirname(stored_state_path),
2825
delete=False) as stored_state:
2826
pickle.dump((clients, client_settings), stored_state)
2827
tempname = stored_state.name
2828
os.rename(tempname, stored_state_path)
2829
except (IOError, OSError) as e:
2835
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2836
logger.warning("Could not save persistent state: {}"
2837
.format(os.strerror(e.errno)))
2839
logger.warning("Could not save persistent state:",
2843
# Delete all clients, and settings from config
1716
2844
while tcp_server.clients:
1717
client = tcp_server.clients.pop()
2845
name, client = tcp_server.clients.popitem()
1719
2847
client.remove_from_connection()
1720
client.disable_hook = None
1721
2848
# Don't signal anything except ClientRemoved
1722
2849
client.disable(quiet=True)
1724
2851
# Emit D-Bus signal
1725
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2852
mandos_dbus_service.ClientRemoved(
2853
client.dbus_object_path, client.name)
2854
client_settings.clear()
1728
2856
atexit.register(cleanup)
1730
for client in tcp_server.clients:
2858
for client in tcp_server.clients.itervalues():
1732
2860
# Emit D-Bus signal
1733
2861
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2862
# Need to initiate checking of clients
2864
client.init_checker()
1736
2866
tcp_server.enable()
1737
2867
tcp_server.server_activate()
1739
2869
# Find out what port we got
1740
service.port = tcp_server.socket.getsockname()[1]
2871
service.port = tcp_server.socket.getsockname()[1]
1742
logger.info(u"Now listening on address %r, port %d,"
1743
" flowinfo %d, scope_id %d"
1744
% tcp_server.socket.getsockname())
2873
logger.info("Now listening on address %r, port %d,"
2874
" flowinfo %d, scope_id %d",
2875
*tcp_server.socket.getsockname())
1746
logger.info(u"Now listening on address %r, port %d"
1747
% tcp_server.socket.getsockname())
2877
logger.info("Now listening on address %r, port %d",
2878
*tcp_server.socket.getsockname())
1749
2880
#service.interface = tcp_server.socket.getsockname()[3]
1752
# From the Avahi example code
1755
except dbus.exceptions.DBusException, error:
1756
logger.critical(u"DBusException: %s", error)
1759
# End of Avahi example code
2884
# From the Avahi example code
2887
except dbus.exceptions.DBusException as error:
2888
logger.critical("D-Bus Exception", exc_info=error)
2891
# End of Avahi example code
1761
2893
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1762
2894
lambda *args, **kwargs:
1763
2895
(tcp_server.handle_request
1764
2896
(*args[2:], **kwargs) or True))
1766
logger.debug(u"Starting main loop")
2898
logger.debug("Starting main loop")
1767
2899
main_loop.run()
1768
except AvahiError, error:
1769
logger.critical(u"AvahiError: %s", error)
2900
except AvahiError as error:
2901
logger.critical("Avahi Error", exc_info=error)
1772
2904
except KeyboardInterrupt:
1775
logger.debug(u"Server received KeyboardInterrupt")
1776
logger.debug(u"Server exiting")
2906
print("", file=sys.stderr)
2907
logger.debug("Server received KeyboardInterrupt")
2908
logger.debug("Server exiting")
1777
2909
# Must run before the D-Bus bus name gets deregistered
1780
2913
if __name__ == '__main__':