270
124
self.rename_count = 0
271
125
self.max_renames = max_renames
272
self.protocol = protocol
273
self.group = None # our entry group
276
self.entry_group_state_changed_match = None
278
126
def rename(self):
279
127
"""Derived from the Avahi example code"""
280
128
if self.rename_count >= self.max_renames:
281
logger.critical("No suitable Zeroconf service name found"
282
" after %i retries, exiting.",
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
283
131
self.rename_count)
284
raise AvahiServiceError("Too many renames")
285
self.name = unicode(self.server
286
.GetAlternativeServiceName(self.name))
287
logger.info("Changing Zeroconf service name to %r ...",
132
raise AvahiServiceError(u"Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
134
logger.info(u"Changing Zeroconf service name to %r ...",
136
syslogger.setFormatter(logging.Formatter
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
292
except dbus.exceptions.DBusException as error:
293
logger.critical("D-Bus Exception", exc_info=error)
296
141
self.rename_count += 1
298
142
def remove(self):
299
143
"""Derived from the Avahi example code"""
300
if self.entry_group_state_changed_match is not None:
301
self.entry_group_state_changed_match.remove()
302
self.entry_group_state_changed_match = None
303
if self.group is not None:
144
if group is not None:
307
147
"""Derived from the Avahi example code"""
309
if self.group is None:
310
self.group = dbus.Interface(
311
self.bus.get_object(avahi.DBUS_NAME,
312
self.server.EntryGroupNew()),
313
avahi.DBUS_INTERFACE_ENTRY_GROUP)
314
self.entry_group_state_changed_match = (
315
self.group.connect_to_signal(
316
'StateChanged', self.entry_group_state_changed))
317
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
318
self.name, self.type)
319
self.group.AddService(
322
dbus.UInt32(0), # flags
323
self.name, self.type,
324
self.domain, self.host,
325
dbus.UInt16(self.port),
326
avahi.string_array_to_txt_array(self.TXT))
329
def entry_group_state_changed(self, state, error):
330
"""Derived from the Avahi example code"""
331
logger.debug("Avahi entry group state change: %i", state)
333
if state == avahi.ENTRY_GROUP_ESTABLISHED:
334
logger.debug("Zeroconf service established.")
335
elif state == avahi.ENTRY_GROUP_COLLISION:
336
logger.info("Zeroconf service name collision.")
338
elif state == avahi.ENTRY_GROUP_FAILURE:
339
logger.critical("Avahi: Error in group state changed %s",
341
raise AvahiGroupError("State changed: {!s}"
345
"""Derived from the Avahi example code"""
346
if self.group is not None:
349
except (dbus.exceptions.UnknownMethodException,
350
dbus.exceptions.DBusException):
355
def server_state_changed(self, state, error=None):
356
"""Derived from the Avahi example code"""
357
logger.debug("Avahi server state change: %i", state)
358
bad_states = { avahi.SERVER_INVALID:
359
"Zeroconf server invalid",
360
avahi.SERVER_REGISTERING: None,
361
avahi.SERVER_COLLISION:
362
"Zeroconf server name collision",
363
avahi.SERVER_FAILURE:
364
"Zeroconf server failure" }
365
if state in bad_states:
366
if bad_states[state] is not None:
368
logger.error(bad_states[state])
370
logger.error(bad_states[state] + ": %r", error)
372
elif state == avahi.SERVER_RUNNING:
376
logger.debug("Unknown state: %r", state)
378
logger.debug("Unknown state: %r: %r", state, error)
381
"""Derived from the Avahi example code"""
382
if self.server is None:
383
self.server = dbus.Interface(
384
self.bus.get_object(avahi.DBUS_NAME,
385
avahi.DBUS_PATH_SERVER,
386
follow_name_owner_changes=True),
387
avahi.DBUS_INTERFACE_SERVER)
388
self.server.connect_to_signal("StateChanged",
389
self.server_state_changed)
390
self.server_state_changed(self.server.GetState())
393
class AvahiServiceToSyslog(AvahiService):
395
"""Add the new name to the syslog messages"""
396
ret = AvahiService.rename(self)
397
syslogger.setFormatter(logging.Formatter
398
('Mandos ({}) [%(process)d]:'
399
' %(levelname)s: %(message)s'
404
class Client(object):
150
group = dbus.Interface(bus.get_object
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
173
def _datetime_to_dbus(dt, variant_level=0):
174
"""Convert a UTC datetime.datetime() to a D-Bus type."""
175
return dbus.String(dt.isoformat(), variant_level=variant_level)
178
class Client(dbus.service.Object):
405
179
"""A representation of a client host served by this server.
408
approved: bool(); 'None' if not yet approved/disapproved
409
approval_delay: datetime.timedelta(); Time to wait for approval
410
approval_duration: datetime.timedelta(); Duration of one approval
181
name: string; from the config file, used in log messages and
183
fingerprint: string (40 or 32 hexadecimal digits); used to
184
uniquely identify the client
185
secret: bytestring; sent verbatim (over TLS) to client
186
host: string; available for use by the checker command
187
created: datetime.datetime(); (UTC) object creation
188
last_enabled: datetime.datetime(); (UTC)
190
last_checked_ok: datetime.datetime(); (UTC) or None
191
timeout: datetime.timedelta(); How long from last_checked_ok
192
until this client is invalid
193
interval: datetime.timedelta(); How often to start a new checker
194
disable_hook: If set, called by disable() as disable_hook(self)
411
195
checker: subprocess.Popen(); a running checker process used
412
196
to see if the client lives.
413
197
'None' if no process is running.
414
checker_callback_tag: a gobject event source tag, or None
415
checker_command: string; External command which is run to check
416
if client lives. %() expansions are done at
198
checker_initiator_tag: a gobject event source tag, or None
199
disable_initiator_tag: - '' -
200
checker_callback_tag: - '' -
201
checker_command: string; External command which is run to check if
202
client lives. %() expansions are done at
417
203
runtime with vars(self) as dict, so that for
418
204
instance %(name)s can be used in the command.
419
checker_initiator_tag: a gobject event source tag, or None
420
created: datetime.datetime(); (UTC) object creation
421
client_structure: Object describing what attributes a client has
422
and is used for storing the client at exit
423
current_checker_command: string; current running checker_command
424
disable_initiator_tag: a gobject event source tag, or None
426
fingerprint: string (40 or 32 hexadecimal digits); used to
427
uniquely identify the client
428
host: string; available for use by the checker command
429
interval: datetime.timedelta(); How often to start a new checker
430
last_approval_request: datetime.datetime(); (UTC) or None
431
last_checked_ok: datetime.datetime(); (UTC) or None
432
last_checker_status: integer between 0 and 255 reflecting exit
433
status of last checker. -1 reflects crashed
434
checker, -2 means no checker completed yet.
435
last_enabled: datetime.datetime(); (UTC) or None
436
name: string; from the config file, used in log messages and
438
secret: bytestring; sent verbatim (over TLS) to client
439
timeout: datetime.timedelta(); How long from last_checked_ok
440
until this client is disabled
441
extended_timeout: extra long timeout when secret has been sent
442
runtime_expansions: Allowed attributes for runtime expansion.
443
expires: datetime.datetime(); time (UTC) when a client will be
445
server_settings: The server_settings dict from main()
205
use_dbus: bool(); Whether to provide D-Bus interface and signals
206
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
448
runtime_expansions = ("approval_delay", "approval_duration",
449
"created", "enabled", "expires",
450
"fingerprint", "host", "interval",
451
"last_approval_request", "last_checked_ok",
452
"last_enabled", "name", "timeout")
453
client_defaults = { "timeout": "PT5M",
454
"extended_timeout": "PT15M",
456
"checker": "fping -q -- %%(host)s",
458
"approval_delay": "PT0S",
459
"approval_duration": "PT1S",
460
"approved_by_default": "True",
465
def config_parser(config):
466
"""Construct a new dict of client settings of this form:
467
{ client_name: {setting_name: value, ...}, ...}
468
with exceptions for any special settings as defined above.
469
NOTE: Must be a pure function. Must return the same result
470
value given the same arguments.
473
for client_name in config.sections():
474
section = dict(config.items(client_name))
475
client = settings[client_name] = {}
477
client["host"] = section["host"]
478
# Reformat values from string types to Python types
479
client["approved_by_default"] = config.getboolean(
480
client_name, "approved_by_default")
481
client["enabled"] = config.getboolean(client_name,
484
client["fingerprint"] = (section["fingerprint"].upper()
486
if "secret" in section:
487
client["secret"] = section["secret"].decode("base64")
488
elif "secfile" in section:
489
with open(os.path.expanduser(os.path.expandvars
490
(section["secfile"])),
492
client["secret"] = secfile.read()
494
raise TypeError("No secret or secfile for section {}"
496
client["timeout"] = string_to_delta(section["timeout"])
497
client["extended_timeout"] = string_to_delta(
498
section["extended_timeout"])
499
client["interval"] = string_to_delta(section["interval"])
500
client["approval_delay"] = string_to_delta(
501
section["approval_delay"])
502
client["approval_duration"] = string_to_delta(
503
section["approval_duration"])
504
client["checker_command"] = section["checker"]
505
client["last_approval_request"] = None
506
client["last_checked_ok"] = None
507
client["last_checker_status"] = -2
511
def __init__(self, settings, name = None, server_settings=None):
208
def timeout_milliseconds(self):
209
"Return the 'timeout' attribute in milliseconds"
210
return ((self.timeout.days * 24 * 60 * 60 * 1000)
211
+ (self.timeout.seconds * 1000)
212
+ (self.timeout.microseconds // 1000))
214
def interval_milliseconds(self):
215
"Return the 'interval' attribute in milliseconds"
216
return ((self.interval.days * 24 * 60 * 60 * 1000)
217
+ (self.interval.seconds * 1000)
218
+ (self.interval.microseconds // 1000))
220
def __init__(self, name = None, disable_hook=None, config=None,
222
"""Note: the 'checker' key in 'config' sets the
223
'checker_command' attribute and *not* the 'checker'
513
if server_settings is None:
515
self.server_settings = server_settings
516
# adding all client settings
517
for setting, value in settings.items():
518
setattr(self, setting, value)
521
if not hasattr(self, "last_enabled"):
522
self.last_enabled = datetime.datetime.utcnow()
523
if not hasattr(self, "expires"):
524
self.expires = (datetime.datetime.utcnow()
527
self.last_enabled = None
530
logger.debug("Creating client %r", self.name)
228
logger.debug(u"Creating client %r", self.name)
229
self.use_dbus = False # During __init__
531
230
# Uppercase and remove spaces from fingerprint for later
532
231
# comparison purposes with return value from the fingerprint()
534
logger.debug(" Fingerprint: %s", self.fingerprint)
535
self.created = settings.get("created",
536
datetime.datetime.utcnow())
538
# attributes specific for this server instance
233
self.fingerprint = (config["fingerprint"].upper()
235
logger.debug(u" Fingerprint: %s", self.fingerprint)
236
if "secret" in config:
237
self.secret = config["secret"].decode(u"base64")
238
elif "secfile" in config:
239
with closing(open(os.path.expanduser
241
(config["secfile"])))) as secfile:
242
self.secret = secfile.read()
244
raise TypeError(u"No secret or secfile for client %s"
246
self.host = config.get("host", "")
247
self.created = datetime.datetime.utcnow()
249
self.last_enabled = None
250
self.last_checked_ok = None
251
self.timeout = string_to_delta(config["timeout"])
252
self.interval = string_to_delta(config["interval"])
253
self.disable_hook = disable_hook
539
254
self.checker = None
540
255
self.checker_initiator_tag = None
541
256
self.disable_initiator_tag = None
542
257
self.checker_callback_tag = None
543
self.current_checker_command = None
545
self.approvals_pending = 0
546
self.changedstate = (multiprocessing_manager
547
.Condition(multiprocessing_manager
549
self.client_structure = [attr for attr in
550
self.__dict__.iterkeys()
551
if not attr.startswith("_")]
552
self.client_structure.append("client_structure")
554
for name, t in inspect.getmembers(type(self),
558
if not name.startswith("_"):
559
self.client_structure.append(name)
561
# Send notice to process children that client state has changed
562
def send_changedstate(self):
563
with self.changedstate:
564
self.changedstate.notify_all()
258
self.checker_command = config["checker"]
259
self.last_connect = None
260
# Only now, when this client is initialized, can it show up on
262
self.use_dbus = use_dbus
264
self.dbus_object_path = (dbus.ObjectPath
266
+ self.name.replace(".", "_")))
267
dbus.service.Object.__init__(self, bus,
268
self.dbus_object_path)
566
270
def enable(self):
567
271
"""Start this client's checker and timeout hooks"""
568
if getattr(self, "enabled", False):
571
self.expires = datetime.datetime.utcnow() + self.timeout
573
272
self.last_enabled = datetime.datetime.utcnow()
575
self.send_changedstate()
577
def disable(self, quiet=True):
578
"""Disable this client."""
579
if not getattr(self, "enabled", False):
582
logger.info("Disabling client %s", self.name)
583
if getattr(self, "disable_initiator_tag", None) is not None:
584
gobject.source_remove(self.disable_initiator_tag)
585
self.disable_initiator_tag = None
587
if getattr(self, "checker_initiator_tag", None) is not None:
588
gobject.source_remove(self.checker_initiator_tag)
589
self.checker_initiator_tag = None
593
self.send_changedstate()
594
# Do not run this again if called by a gobject.timeout_add
600
def init_checker(self):
601
273
# Schedule a new checker to be started an 'interval' from now,
602
274
# and every interval from then on.
603
if self.checker_initiator_tag is not None:
604
gobject.source_remove(self.checker_initiator_tag)
605
275
self.checker_initiator_tag = (gobject.timeout_add
607
.total_seconds() * 1000),
276
(self.interval_milliseconds(),
608
277
self.start_checker))
278
# Also start a new checker *right now*.
609
280
# Schedule a disable() when 'timeout' has passed
610
if self.disable_initiator_tag is not None:
281
self.disable_initiator_tag = (gobject.timeout_add
282
(self.timeout_milliseconds(),
287
self.PropertyChanged(dbus.String(u"enabled"),
288
dbus.Boolean(True, variant_level=1))
289
self.PropertyChanged(dbus.String(u"last_enabled"),
290
(_datetime_to_dbus(self.last_enabled,
294
"""Disable this client."""
295
if not getattr(self, "enabled", False):
297
logger.info(u"Disabling client %s", self.name)
298
if getattr(self, "disable_initiator_tag", False):
611
299
gobject.source_remove(self.disable_initiator_tag)
612
self.disable_initiator_tag = (gobject.timeout_add
614
.total_seconds() * 1000),
616
# Also start a new checker *right now*.
300
self.disable_initiator_tag = None
301
if getattr(self, "checker_initiator_tag", False):
302
gobject.source_remove(self.checker_initiator_tag)
303
self.checker_initiator_tag = None
305
if self.disable_hook:
306
self.disable_hook(self)
310
self.PropertyChanged(dbus.String(u"enabled"),
311
dbus.Boolean(False, variant_level=1))
312
# Do not run this again if called by a gobject.timeout_add
316
self.disable_hook = None
619
319
def checker_callback(self, pid, condition, command):
620
320
"""The checker has completed, so take appropriate actions."""
621
321
self.checker_callback_tag = None
622
322
self.checker = None
325
self.PropertyChanged(dbus.String(u"checker_running"),
326
dbus.Boolean(False, variant_level=1))
623
327
if os.WIFEXITED(condition):
624
self.last_checker_status = os.WEXITSTATUS(condition)
625
if self.last_checker_status == 0:
626
logger.info("Checker for %(name)s succeeded",
328
exitstatus = os.WEXITSTATUS(condition)
330
logger.info(u"Checker for %(name)s succeeded",
628
332
self.checked_ok()
630
logger.info("Checker for %(name)s failed",
334
logger.info(u"Checker for %(name)s failed",
338
self.CheckerCompleted(dbus.Int16(exitstatus),
339
dbus.Int64(condition),
340
dbus.String(command))
633
self.last_checker_status = -1
634
logger.warning("Checker for %(name)s crashed?",
342
logger.warning(u"Checker for %(name)s crashed?",
346
self.CheckerCompleted(dbus.Int16(-1),
347
dbus.Int64(condition),
348
dbus.String(command))
637
350
def checked_ok(self):
638
"""Assert that the client has been seen, alive and well."""
351
"""Bump up the timeout for this client.
352
This should only be called when the client has been seen,
639
355
self.last_checked_ok = datetime.datetime.utcnow()
640
self.last_checker_status = 0
643
def bump_timeout(self, timeout=None):
644
"""Bump up the timeout for this client."""
646
timeout = self.timeout
647
if self.disable_initiator_tag is not None:
648
gobject.source_remove(self.disable_initiator_tag)
649
self.disable_initiator_tag = None
650
if getattr(self, "enabled", False):
651
self.disable_initiator_tag = (gobject.timeout_add
652
(int(timeout.total_seconds()
653
* 1000), self.disable))
654
self.expires = datetime.datetime.utcnow() + timeout
656
def need_approval(self):
657
self.last_approval_request = datetime.datetime.utcnow()
356
gobject.source_remove(self.disable_initiator_tag)
357
self.disable_initiator_tag = (gobject.timeout_add
358
(self.timeout_milliseconds(),
362
self.PropertyChanged(
363
dbus.String(u"last_checked_ok"),
364
(_datetime_to_dbus(self.last_checked_ok,
659
367
def start_checker(self):
660
368
"""Start a new checker subprocess if one is not running.
662
369
If a checker already exists, leave it running and do
664
371
# The reason for not killing a running checker is that if we
665
# did that, and if a checker (for some reason) started running
666
# slowly and taking more than 'interval' time, then the client
667
# would inevitably timeout, since no checker would get a
668
# chance to run to completion. If we instead leave running
372
# did that, then if a checker (for some reason) started
373
# running slowly and taking more than 'interval' time, the
374
# client would inevitably timeout, since no checker would get
375
# a chance to run to completion. If we instead leave running
669
376
# checkers alone, the checker would have to take more time
670
# than 'timeout' for the client to be disabled, which is as it
673
# If a checker exists, make sure it is not a zombie
675
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
676
except AttributeError:
678
except OSError as error:
679
if error.errno != errno.ECHILD:
683
logger.warning("Checker was a zombie")
684
gobject.source_remove(self.checker_callback_tag)
685
self.checker_callback(pid, status,
686
self.current_checker_command)
687
# Start a new checker if needed
377
# than 'timeout' for the client to be declared invalid, which
378
# is as it should be.
688
379
if self.checker is None:
689
# Escape attributes for the shell
690
escaped_attrs = { attr:
691
re.escape(unicode(getattr(self,
693
for attr in self.runtime_expansions }
695
command = self.checker_command % escaped_attrs
696
except TypeError as error:
697
logger.error('Could not format string "%s"',
698
self.checker_command, exc_info=error)
699
return True # Try again later
700
self.current_checker_command = command
702
logger.info("Starting checker %r for %s",
381
# In case checker_command has exactly one % operator
382
command = self.checker_command % self.host
384
# Escape attributes for the shell
385
escaped_attrs = dict((key, re.escape(str(val)))
387
vars(self).iteritems())
389
command = self.checker_command % escaped_attrs
390
except TypeError, error:
391
logger.error(u'Could not format string "%s":'
392
u' %s', self.checker_command, error)
393
return True # Try again later
395
logger.info(u"Starting checker %r for %s",
703
396
command, self.name)
704
397
# We don't need to redirect stdout and stderr, since
705
398
# in normal mode, that is already done by daemon(),
706
399
# and in debug mode we don't want to. (Stdin is
707
400
# always replaced by /dev/null.)
708
# The exception is when not debugging but nevertheless
709
# running in the foreground; use the previously
712
if (not self.server_settings["debug"]
713
and self.server_settings["foreground"]):
714
popen_args.update({"stdout": wnull,
716
401
self.checker = subprocess.Popen(command,
720
except OSError as error:
721
logger.error("Failed to start subprocess",
724
self.checker_callback_tag = (gobject.child_watch_add
726
self.checker_callback,
728
# The checker may have completed before the gobject
729
# watch was added. Check for this.
731
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
732
except OSError as error:
733
if error.errno == errno.ECHILD:
734
# This should never happen
735
logger.error("Child process vanished",
740
gobject.source_remove(self.checker_callback_tag)
741
self.checker_callback(pid, status, command)
406
self.CheckerStarted(command)
407
self.PropertyChanged(
408
dbus.String("checker_running"),
409
dbus.Boolean(True, variant_level=1))
410
self.checker_callback_tag = (gobject.child_watch_add
412
self.checker_callback,
414
except OSError, error:
415
logger.error(u"Failed to start subprocess: %s",
742
417
# Re-run this periodically if run by gobject.timeout_add
749
424
self.checker_callback_tag = None
750
425
if getattr(self, "checker", None) is None:
752
logger.debug("Stopping checker for %(name)s", vars(self))
427
logger.debug(u"Stopping checker for %(name)s", vars(self))
754
self.checker.terminate()
429
os.kill(self.checker.pid, signal.SIGTERM)
756
431
#if self.checker.poll() is None:
757
# self.checker.kill()
758
except OSError as error:
432
# os.kill(self.checker.pid, signal.SIGKILL)
433
except OSError, error:
759
434
if error.errno != errno.ESRCH: # No such process
761
436
self.checker = None
764
def dbus_service_property(dbus_interface, signature="v",
765
access="readwrite", byte_arrays=False):
766
"""Decorators for marking methods of a DBusObjectWithProperties to
767
become properties on the D-Bus.
769
The decorated method will be called with no arguments by "Get"
770
and with one argument by "Set".
772
The parameters, where they are supported, are the same as
773
dbus.service.method, except there is only "signature", since the
774
type from Get() and the type sent to Set() is the same.
776
# Encoding deeply encoded byte arrays is not supported yet by the
777
# "Set" method, so we fail early here:
778
if byte_arrays and signature != "ay":
779
raise ValueError("Byte arrays not supported for non-'ay'"
780
" signature {!r}".format(signature))
782
func._dbus_is_property = True
783
func._dbus_interface = dbus_interface
784
func._dbus_signature = signature
785
func._dbus_access = access
786
func._dbus_name = func.__name__
787
if func._dbus_name.endswith("_dbus_property"):
788
func._dbus_name = func._dbus_name[:-14]
789
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
794
def dbus_interface_annotations(dbus_interface):
795
"""Decorator for marking functions returning interface annotations
799
@dbus_interface_annotations("org.example.Interface")
800
def _foo(self): # Function name does not matter
801
return {"org.freedesktop.DBus.Deprecated": "true",
802
"org.freedesktop.DBus.Property.EmitsChangedSignal":
806
func._dbus_is_interface = True
807
func._dbus_interface = dbus_interface
808
func._dbus_name = dbus_interface
813
def dbus_annotations(annotations):
814
"""Decorator to annotate D-Bus methods, signals or properties
817
@dbus_service_property("org.example.Interface", signature="b",
819
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
820
"org.freedesktop.DBus.Property."
821
"EmitsChangedSignal": "false"})
822
def Property_dbus_property(self):
823
return dbus.Boolean(False)
826
func._dbus_annotations = annotations
831
class DBusPropertyException(dbus.exceptions.DBusException):
832
"""A base class for D-Bus property-related exceptions
834
def __unicode__(self):
835
return unicode(str(self))
838
class DBusPropertyAccessException(DBusPropertyException):
839
"""A property's access permissions disallows an operation.
844
class DBusPropertyNotFound(DBusPropertyException):
845
"""An attempt was made to access a non-existing property.
850
class DBusObjectWithProperties(dbus.service.Object):
851
"""A D-Bus object with properties.
853
Classes inheriting from this can use the dbus_service_property
854
decorator to expose methods as D-Bus properties. It exposes the
855
standard Get(), Set(), and GetAll() methods on the D-Bus.
859
def _is_dbus_thing(thing):
860
"""Returns a function testing if an attribute is a D-Bus thing
862
If called like _is_dbus_thing("method") it returns a function
863
suitable for use as predicate to inspect.getmembers().
865
return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
868
def _get_all_dbus_things(self, thing):
869
"""Returns a generator of (name, attribute) pairs
871
return ((getattr(athing.__get__(self), "_dbus_name",
873
athing.__get__(self))
874
for cls in self.__class__.__mro__
876
inspect.getmembers(cls,
877
self._is_dbus_thing(thing)))
879
def _get_dbus_property(self, interface_name, property_name):
880
"""Returns a bound method if one exists which is a D-Bus
881
property with the specified name and interface.
883
for cls in self.__class__.__mro__:
884
for name, value in (inspect.getmembers
886
self._is_dbus_thing("property"))):
887
if (value._dbus_name == property_name
888
and value._dbus_interface == interface_name):
889
return value.__get__(self)
892
raise DBusPropertyNotFound(self.dbus_object_path + ":"
893
+ interface_name + "."
896
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
898
def Get(self, interface_name, property_name):
899
"""Standard D-Bus property Get() method, see D-Bus standard.
901
prop = self._get_dbus_property(interface_name, property_name)
902
if prop._dbus_access == "write":
903
raise DBusPropertyAccessException(property_name)
905
if not hasattr(value, "variant_level"):
907
return type(value)(value, variant_level=value.variant_level+1)
909
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
910
def Set(self, interface_name, property_name, value):
911
"""Standard D-Bus property Set() method, see D-Bus standard.
913
prop = self._get_dbus_property(interface_name, property_name)
914
if prop._dbus_access == "read":
915
raise DBusPropertyAccessException(property_name)
916
if prop._dbus_get_args_options["byte_arrays"]:
917
# The byte_arrays option is not supported yet on
918
# signatures other than "ay".
919
if prop._dbus_signature != "ay":
920
raise ValueError("Byte arrays not supported for non-"
921
"'ay' signature {!r}"
922
.format(prop._dbus_signature))
923
value = dbus.ByteArray(b''.join(chr(byte)
927
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
928
out_signature="a{sv}")
929
def GetAll(self, interface_name):
930
"""Standard D-Bus property GetAll() method, see D-Bus
933
Note: Will not include properties with access="write".
936
for name, prop in self._get_all_dbus_things("property"):
938
and interface_name != prop._dbus_interface):
939
# Interface non-empty but did not match
941
# Ignore write-only properties
942
if prop._dbus_access == "write":
945
if not hasattr(value, "variant_level"):
946
properties[name] = value
948
properties[name] = type(value)(value, variant_level=
949
value.variant_level+1)
950
return dbus.Dictionary(properties, signature="sv")
952
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
954
path_keyword='object_path',
955
connection_keyword='connection')
956
def Introspect(self, object_path, connection):
957
"""Overloading of standard D-Bus method.
959
Inserts property tags and interface annotation tags.
961
xmlstring = dbus.service.Object.Introspect(self, object_path,
964
document = xml.dom.minidom.parseString(xmlstring)
965
def make_tag(document, name, prop):
966
e = document.createElement("property")
967
e.setAttribute("name", name)
968
e.setAttribute("type", prop._dbus_signature)
969
e.setAttribute("access", prop._dbus_access)
971
for if_tag in document.getElementsByTagName("interface"):
973
for tag in (make_tag(document, name, prop)
975
in self._get_all_dbus_things("property")
976
if prop._dbus_interface
977
== if_tag.getAttribute("name")):
978
if_tag.appendChild(tag)
979
# Add annotation tags
980
for typ in ("method", "signal", "property"):
981
for tag in if_tag.getElementsByTagName(typ):
983
for name, prop in (self.
984
_get_all_dbus_things(typ)):
985
if (name == tag.getAttribute("name")
986
and prop._dbus_interface
987
== if_tag.getAttribute("name")):
988
annots.update(getattr
992
for name, value in annots.items():
993
ann_tag = document.createElement(
995
ann_tag.setAttribute("name", name)
996
ann_tag.setAttribute("value", value)
997
tag.appendChild(ann_tag)
998
# Add interface annotation tags
999
for annotation, value in dict(
1000
itertools.chain.from_iterable(
1001
annotations().items()
1002
for name, annotations in
1003
self._get_all_dbus_things("interface")
1004
if name == if_tag.getAttribute("name")
1006
ann_tag = document.createElement("annotation")
1007
ann_tag.setAttribute("name", annotation)
1008
ann_tag.setAttribute("value", value)
1009
if_tag.appendChild(ann_tag)
1010
# Add the names to the return values for the
1011
# "org.freedesktop.DBus.Properties" methods
1012
if (if_tag.getAttribute("name")
1013
== "org.freedesktop.DBus.Properties"):
1014
for cn in if_tag.getElementsByTagName("method"):
1015
if cn.getAttribute("name") == "Get":
1016
for arg in cn.getElementsByTagName("arg"):
1017
if (arg.getAttribute("direction")
1019
arg.setAttribute("name", "value")
1020
elif cn.getAttribute("name") == "GetAll":
1021
for arg in cn.getElementsByTagName("arg"):
1022
if (arg.getAttribute("direction")
1024
arg.setAttribute("name", "props")
1025
xmlstring = document.toxml("utf-8")
1027
except (AttributeError, xml.dom.DOMException,
1028
xml.parsers.expat.ExpatError) as error:
1029
logger.error("Failed to override Introspection method",
1034
def datetime_to_dbus(dt, variant_level=0):
1035
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1037
return dbus.String("", variant_level = variant_level)
1038
return dbus.String(dt.isoformat(),
1039
variant_level=variant_level)
1042
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1043
"""A class decorator; applied to a subclass of
1044
dbus.service.Object, it will add alternate D-Bus attributes with
1045
interface names according to the "alt_interface_names" mapping.
1048
@alternate_dbus_interfaces({"org.example.Interface":
1049
"net.example.AlternateInterface"})
1050
class SampleDBusObject(dbus.service.Object):
1051
@dbus.service.method("org.example.Interface")
1052
def SampleDBusMethod():
1055
The above "SampleDBusMethod" on "SampleDBusObject" will be
1056
reachable via two interfaces: "org.example.Interface" and
1057
"net.example.AlternateInterface", the latter of which will have
1058
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1059
"true", unless "deprecate" is passed with a False value.
1061
This works for methods and signals, and also for D-Bus properties
1062
(from DBusObjectWithProperties) and interfaces (from the
1063
dbus_interface_annotations decorator).
1066
for orig_interface_name, alt_interface_name in (
1067
alt_interface_names.items()):
1069
interface_names = set()
1070
# Go though all attributes of the class
1071
for attrname, attribute in inspect.getmembers(cls):
1072
# Ignore non-D-Bus attributes, and D-Bus attributes
1073
# with the wrong interface name
1074
if (not hasattr(attribute, "_dbus_interface")
1075
or not attribute._dbus_interface
1076
.startswith(orig_interface_name)):
1078
# Create an alternate D-Bus interface name based on
1080
alt_interface = (attribute._dbus_interface
1081
.replace(orig_interface_name,
1082
alt_interface_name))
1083
interface_names.add(alt_interface)
1084
# Is this a D-Bus signal?
1085
if getattr(attribute, "_dbus_is_signal", False):
1086
# Extract the original non-method undecorated
1087
# function by black magic
1088
nonmethod_func = (dict(
1089
zip(attribute.func_code.co_freevars,
1090
attribute.__closure__))["func"]
1092
# Create a new, but exactly alike, function
1093
# object, and decorate it to be a new D-Bus signal
1094
# with the alternate D-Bus interface name
1095
new_function = (dbus.service.signal
1097
attribute._dbus_signature)
1098
(types.FunctionType(
1099
nonmethod_func.func_code,
1100
nonmethod_func.func_globals,
1101
nonmethod_func.func_name,
1102
nonmethod_func.func_defaults,
1103
nonmethod_func.func_closure)))
1104
# Copy annotations, if any
1106
new_function._dbus_annotations = (
1107
dict(attribute._dbus_annotations))
1108
except AttributeError:
1110
# Define a creator of a function to call both the
1111
# original and alternate functions, so both the
1112
# original and alternate signals gets sent when
1113
# the function is called
1114
def fixscope(func1, func2):
1115
"""This function is a scope container to pass
1116
func1 and func2 to the "call_both" function
1117
outside of its arguments"""
1118
def call_both(*args, **kwargs):
1119
"""This function will emit two D-Bus
1120
signals by calling func1 and func2"""
1121
func1(*args, **kwargs)
1122
func2(*args, **kwargs)
1124
# Create the "call_both" function and add it to
1126
attr[attrname] = fixscope(attribute, new_function)
1127
# Is this a D-Bus method?
1128
elif getattr(attribute, "_dbus_is_method", False):
1129
# Create a new, but exactly alike, function
1130
# object. Decorate it to be a new D-Bus method
1131
# with the alternate D-Bus interface name. Add it
1133
attr[attrname] = (dbus.service.method
1135
attribute._dbus_in_signature,
1136
attribute._dbus_out_signature)
1138
(attribute.func_code,
1139
attribute.func_globals,
1140
attribute.func_name,
1141
attribute.func_defaults,
1142
attribute.func_closure)))
1143
# Copy annotations, if any
1145
attr[attrname]._dbus_annotations = (
1146
dict(attribute._dbus_annotations))
1147
except AttributeError:
1149
# Is this a D-Bus property?
1150
elif getattr(attribute, "_dbus_is_property", False):
1151
# Create a new, but exactly alike, function
1152
# object, and decorate it to be a new D-Bus
1153
# property with the alternate D-Bus interface
1154
# name. Add it to the class.
1155
attr[attrname] = (dbus_service_property
1157
attribute._dbus_signature,
1158
attribute._dbus_access,
1160
._dbus_get_args_options
1163
(attribute.func_code,
1164
attribute.func_globals,
1165
attribute.func_name,
1166
attribute.func_defaults,
1167
attribute.func_closure)))
1168
# Copy annotations, if any
1170
attr[attrname]._dbus_annotations = (
1171
dict(attribute._dbus_annotations))
1172
except AttributeError:
1174
# Is this a D-Bus interface?
1175
elif getattr(attribute, "_dbus_is_interface", False):
1176
# Create a new, but exactly alike, function
1177
# object. Decorate it to be a new D-Bus interface
1178
# with the alternate D-Bus interface name. Add it
1180
attr[attrname] = (dbus_interface_annotations
1183
(attribute.func_code,
1184
attribute.func_globals,
1185
attribute.func_name,
1186
attribute.func_defaults,
1187
attribute.func_closure)))
1189
# Deprecate all alternate interfaces
1190
iname="_AlternateDBusNames_interface_annotation{}"
1191
for interface_name in interface_names:
1192
@dbus_interface_annotations(interface_name)
1194
return { "org.freedesktop.DBus.Deprecated":
1196
# Find an unused name
1197
for aname in (iname.format(i)
1198
for i in itertools.count()):
1199
if aname not in attr:
1203
# Replace the class with a new subclass of it with
1204
# methods, signals, etc. as created above.
1205
cls = type(b"{}Alternate".format(cls.__name__),
1211
@alternate_dbus_interfaces({"se.recompile.Mandos":
1212
"se.bsnet.fukt.Mandos"})
1213
class ClientDBus(Client, DBusObjectWithProperties):
1214
"""A Client class using D-Bus
1217
dbus_object_path: dbus.ObjectPath
1218
bus: dbus.SystemBus()
1221
runtime_expansions = (Client.runtime_expansions
1222
+ ("dbus_object_path",))
1224
# dbus.service.Object doesn't use super(), so we can't either.
1226
def __init__(self, bus = None, *args, **kwargs):
1228
Client.__init__(self, *args, **kwargs)
1229
# Only now, when this client is initialized, can it show up on
1231
client_object_name = unicode(self.name).translate(
1232
{ord("."): ord("_"),
1233
ord("-"): ord("_")})
1234
self.dbus_object_path = (dbus.ObjectPath
1235
("/clients/" + client_object_name))
1236
DBusObjectWithProperties.__init__(self, self.bus,
1237
self.dbus_object_path)
1239
def notifychangeproperty(transform_func,
1240
dbus_name, type_func=lambda x: x,
1242
""" Modify a variable so that it's a property which announces
1243
its changes to DBus.
1245
transform_fun: Function that takes a value and a variant_level
1246
and transforms it to a D-Bus type.
1247
dbus_name: D-Bus name of the variable
1248
type_func: Function that transform the value before sending it
1249
to the D-Bus. Default: no transform
1250
variant_level: D-Bus variant level. Default: 1
1252
attrname = "_{}".format(dbus_name)
1253
def setter(self, value):
1254
if hasattr(self, "dbus_object_path"):
1255
if (not hasattr(self, attrname) or
1256
type_func(getattr(self, attrname, None))
1257
!= type_func(value)):
1258
dbus_value = transform_func(type_func(value),
1261
self.PropertyChanged(dbus.String(dbus_name),
1263
setattr(self, attrname, value)
1265
return property(lambda self: getattr(self, attrname), setter)
1267
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1268
approvals_pending = notifychangeproperty(dbus.Boolean,
1271
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1272
last_enabled = notifychangeproperty(datetime_to_dbus,
1274
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1275
type_func = lambda checker:
1276
checker is not None)
1277
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1279
last_checker_status = notifychangeproperty(dbus.Int16,
1280
"LastCheckerStatus")
1281
last_approval_request = notifychangeproperty(
1282
datetime_to_dbus, "LastApprovalRequest")
1283
approved_by_default = notifychangeproperty(dbus.Boolean,
1284
"ApprovedByDefault")
1285
approval_delay = notifychangeproperty(dbus.UInt64,
1288
lambda td: td.total_seconds()
1290
approval_duration = notifychangeproperty(
1291
dbus.UInt64, "ApprovalDuration",
1292
type_func = lambda td: td.total_seconds() * 1000)
1293
host = notifychangeproperty(dbus.String, "Host")
1294
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1295
type_func = lambda td:
1296
td.total_seconds() * 1000)
1297
extended_timeout = notifychangeproperty(
1298
dbus.UInt64, "ExtendedTimeout",
1299
type_func = lambda td: td.total_seconds() * 1000)
1300
interval = notifychangeproperty(dbus.UInt64,
1303
lambda td: td.total_seconds()
1305
checker_command = notifychangeproperty(dbus.String, "Checker")
1307
del notifychangeproperty
1309
def __del__(self, *args, **kwargs):
1311
self.remove_from_connection()
1314
if hasattr(DBusObjectWithProperties, "__del__"):
1315
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1316
Client.__del__(self, *args, **kwargs)
1318
def checker_callback(self, pid, condition, command,
1320
self.checker_callback_tag = None
1322
if os.WIFEXITED(condition):
1323
exitstatus = os.WEXITSTATUS(condition)
1325
self.CheckerCompleted(dbus.Int16(exitstatus),
1326
dbus.Int64(condition),
1327
dbus.String(command))
438
self.PropertyChanged(dbus.String(u"checker_running"),
439
dbus.Boolean(False, variant_level=1))
441
def still_valid(self):
442
"""Has the timeout not yet passed for this client?"""
443
if not getattr(self, "enabled", False):
445
now = datetime.datetime.utcnow()
446
if self.last_checked_ok is None:
447
return now < (self.created + self.timeout)
1330
self.CheckerCompleted(dbus.Int16(-1),
1331
dbus.Int64(condition),
1332
dbus.String(command))
1334
return Client.checker_callback(self, pid, condition, command,
1337
def start_checker(self, *args, **kwargs):
1338
old_checker_pid = getattr(self.checker, "pid", None)
1339
r = Client.start_checker(self, *args, **kwargs)
1340
# Only if new checker process was started
1341
if (self.checker is not None
1342
and old_checker_pid != self.checker.pid):
1344
self.CheckerStarted(self.current_checker_command)
1347
def _reset_approved(self):
1348
self.approved = None
1351
def approve(self, value=True):
1352
self.approved = value
1353
gobject.timeout_add(int(self.approval_duration.total_seconds()
1354
* 1000), self._reset_approved)
1355
self.send_changedstate()
1357
## D-Bus methods, signals & properties
1358
_interface = "se.recompile.Mandos.Client"
1362
@dbus_interface_annotations(_interface)
1364
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
449
return now < (self.last_checked_ok + self.timeout)
451
## D-Bus methods & signals
452
_interface = u"se.bsnet.fukt.Mandos.Client"
455
CheckedOK = dbus.service.method(_interface)(checked_ok)
456
CheckedOK.__name__ = "CheckedOK"
1369
458
# CheckerCompleted - signal
1370
459
@dbus.service.signal(_interface, signature="nxs")
1438
583
# StopChecker - method
1439
@dbus.service.method(_interface)
1440
def StopChecker(self):
1445
# ApprovalPending - property
1446
@dbus_service_property(_interface, signature="b", access="read")
1447
def ApprovalPending_dbus_property(self):
1448
return dbus.Boolean(bool(self.approvals_pending))
1450
# ApprovedByDefault - property
1451
@dbus_service_property(_interface, signature="b",
1453
def ApprovedByDefault_dbus_property(self, value=None):
1454
if value is None: # get
1455
return dbus.Boolean(self.approved_by_default)
1456
self.approved_by_default = bool(value)
1458
# ApprovalDelay - property
1459
@dbus_service_property(_interface, signature="t",
1461
def ApprovalDelay_dbus_property(self, value=None):
1462
if value is None: # get
1463
return dbus.UInt64(self.approval_delay.total_seconds()
1465
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1467
# ApprovalDuration - property
1468
@dbus_service_property(_interface, signature="t",
1470
def ApprovalDuration_dbus_property(self, value=None):
1471
if value is None: # get
1472
return dbus.UInt64(self.approval_duration.total_seconds()
1474
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1477
@dbus_service_property(_interface, signature="s", access="read")
1478
def Name_dbus_property(self):
1479
return dbus.String(self.name)
1481
# Fingerprint - property
1482
@dbus_service_property(_interface, signature="s", access="read")
1483
def Fingerprint_dbus_property(self):
1484
return dbus.String(self.fingerprint)
1487
@dbus_service_property(_interface, signature="s",
1489
def Host_dbus_property(self, value=None):
1490
if value is None: # get
1491
return dbus.String(self.host)
1492
self.host = unicode(value)
1494
# Created - property
1495
@dbus_service_property(_interface, signature="s", access="read")
1496
def Created_dbus_property(self):
1497
return datetime_to_dbus(self.created)
1499
# LastEnabled - property
1500
@dbus_service_property(_interface, signature="s", access="read")
1501
def LastEnabled_dbus_property(self):
1502
return datetime_to_dbus(self.last_enabled)
1504
# Enabled - property
1505
@dbus_service_property(_interface, signature="b",
1507
def Enabled_dbus_property(self, value=None):
1508
if value is None: # get
1509
return dbus.Boolean(self.enabled)
1515
# LastCheckedOK - property
1516
@dbus_service_property(_interface, signature="s",
1518
def LastCheckedOK_dbus_property(self, value=None):
1519
if value is not None:
1522
return datetime_to_dbus(self.last_checked_ok)
1524
# LastCheckerStatus - property
1525
@dbus_service_property(_interface, signature="n",
1527
def LastCheckerStatus_dbus_property(self):
1528
return dbus.Int16(self.last_checker_status)
1530
# Expires - property
1531
@dbus_service_property(_interface, signature="s", access="read")
1532
def Expires_dbus_property(self):
1533
return datetime_to_dbus(self.expires)
1535
# LastApprovalRequest - property
1536
@dbus_service_property(_interface, signature="s", access="read")
1537
def LastApprovalRequest_dbus_property(self):
1538
return datetime_to_dbus(self.last_approval_request)
1540
# Timeout - property
1541
@dbus_service_property(_interface, signature="t",
1543
def Timeout_dbus_property(self, value=None):
1544
if value is None: # get
1545
return dbus.UInt64(self.timeout.total_seconds() * 1000)
1546
old_timeout = self.timeout
1547
self.timeout = datetime.timedelta(0, 0, 0, value)
1548
# Reschedule disabling
1550
now = datetime.datetime.utcnow()
1551
self.expires += self.timeout - old_timeout
1552
if self.expires <= now:
1553
# The timeout has passed
1556
if (getattr(self, "disable_initiator_tag", None)
1559
gobject.source_remove(self.disable_initiator_tag)
1560
self.disable_initiator_tag = (
1561
gobject.timeout_add(
1562
int((self.expires - now).total_seconds()
1563
* 1000), self.disable))
1565
# ExtendedTimeout - property
1566
@dbus_service_property(_interface, signature="t",
1568
def ExtendedTimeout_dbus_property(self, value=None):
1569
if value is None: # get
1570
return dbus.UInt64(self.extended_timeout.total_seconds()
1572
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1574
# Interval - property
1575
@dbus_service_property(_interface, signature="t",
1577
def Interval_dbus_property(self, value=None):
1578
if value is None: # get
1579
return dbus.UInt64(self.interval.total_seconds() * 1000)
1580
self.interval = datetime.timedelta(0, 0, 0, value)
1581
if getattr(self, "checker_initiator_tag", None) is None:
1584
# Reschedule checker run
1585
gobject.source_remove(self.checker_initiator_tag)
1586
self.checker_initiator_tag = (gobject.timeout_add
1587
(value, self.start_checker))
1588
self.start_checker() # Start one now, too
1590
# Checker - property
1591
@dbus_service_property(_interface, signature="s",
1593
def Checker_dbus_property(self, value=None):
1594
if value is None: # get
1595
return dbus.String(self.checker_command)
1596
self.checker_command = unicode(value)
1598
# CheckerRunning - property
1599
@dbus_service_property(_interface, signature="b",
1601
def CheckerRunning_dbus_property(self, value=None):
1602
if value is None: # get
1603
return dbus.Boolean(self.checker is not None)
1605
self.start_checker()
1609
# ObjectPath - property
1610
@dbus_service_property(_interface, signature="o", access="read")
1611
def ObjectPath_dbus_property(self):
1612
return self.dbus_object_path # is already a dbus.ObjectPath
1615
@dbus_service_property(_interface, signature="ay",
1616
access="write", byte_arrays=True)
1617
def Secret_dbus_property(self, value):
1618
self.secret = str(value)
584
StopChecker = dbus.service.method(_interface)(stop_checker)
585
StopChecker.__name__ = "StopChecker"
1623
class ProxyClient(object):
1624
def __init__(self, child_pipe, fpr, address):
1625
self._pipe = child_pipe
1626
self._pipe.send(('init', fpr, address))
1627
if not self._pipe.recv():
1630
def __getattribute__(self, name):
1632
return super(ProxyClient, self).__getattribute__(name)
1633
self._pipe.send(('getattr', name))
1634
data = self._pipe.recv()
1635
if data[0] == 'data':
1637
if data[0] == 'function':
1638
def func(*args, **kwargs):
1639
self._pipe.send(('funcall', name, args, kwargs))
1640
return self._pipe.recv()[1]
1643
def __setattr__(self, name, value):
1645
return super(ProxyClient, self).__setattr__(name, value)
1646
self._pipe.send(('setattr', name, value))
1649
class ClientHandler(socketserver.BaseRequestHandler, object):
1650
"""A class to handle client connections.
1652
Instantiated once for each connection to handle it.
590
def peer_certificate(session):
591
"Return the peer's OpenPGP certificate as a bytestring"
592
# If not an OpenPGP certificate...
593
if (gnutls.library.functions
594
.gnutls_certificate_type_get(session._c_object)
595
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
596
# ...do the normal thing
597
return session.peer_certificate
598
list_size = ctypes.c_uint(1)
599
cert_list = (gnutls.library.functions
600
.gnutls_certificate_get_peers
601
(session._c_object, ctypes.byref(list_size)))
602
if not bool(cert_list) and list_size.value != 0:
603
raise gnutls.errors.GNUTLSError("error getting peer"
605
if list_size.value == 0:
608
return ctypes.string_at(cert.data, cert.size)
611
def fingerprint(openpgp):
612
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
613
# New GnuTLS "datum" with the OpenPGP public key
614
datum = (gnutls.library.types
615
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
618
ctypes.c_uint(len(openpgp))))
619
# New empty GnuTLS certificate
620
crt = gnutls.library.types.gnutls_openpgp_crt_t()
621
(gnutls.library.functions
622
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
623
# Import the OpenPGP public key into the certificate
624
(gnutls.library.functions
625
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
626
gnutls.library.constants
627
.GNUTLS_OPENPGP_FMT_RAW))
628
# Verify the self signature in the key
629
crtverify = ctypes.c_uint()
630
(gnutls.library.functions
631
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
632
if crtverify.value != 0:
633
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
634
raise gnutls.errors.CertificateSecurityError("Verify failed")
635
# New buffer for the fingerprint
636
buf = ctypes.create_string_buffer(20)
637
buf_len = ctypes.c_size_t()
638
# Get the fingerprint from the certificate into the buffer
639
(gnutls.library.functions
640
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
641
ctypes.byref(buf_len)))
642
# Deinit the certificate
643
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
644
# Convert the buffer to a Python bytestring
645
fpr = ctypes.string_at(buf, buf_len.value)
646
# Convert the bytestring to hexadecimal notation
647
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
651
class TCP_handler(SocketServer.BaseRequestHandler, object):
652
"""A TCP request handler class.
653
Instantiated by IPv6_TCPServer for each request to handle it.
1653
654
Note: This will run in its own forked process."""
1655
656
def handle(self):
1656
with contextlib.closing(self.server.child_pipe) as child_pipe:
1657
logger.info("TCP connection from: %s",
1658
unicode(self.client_address))
1659
logger.debug("Pipe FD: %d",
1660
self.server.child_pipe.fileno())
1662
session = (gnutls.connection
1663
.ClientSession(self.request,
1665
.X509Credentials()))
1667
# Note: gnutls.connection.X509Credentials is really a
1668
# generic GnuTLS certificate credentials object so long as
1669
# no X.509 keys are added to it. Therefore, we can use it
1670
# here despite using OpenPGP certificates.
1672
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1673
# "+AES-256-CBC", "+SHA1",
1674
# "+COMP-NULL", "+CTYPE-OPENPGP",
1676
# Use a fallback default, since this MUST be set.
1677
priority = self.server.gnutls_priority
1678
if priority is None:
1680
(gnutls.library.functions
1681
.gnutls_priority_set_direct(session._c_object,
1684
# Start communication using the Mandos protocol
1685
# Get protocol number
1686
line = self.request.makefile().readline()
1687
logger.debug("Protocol version: %r", line)
1689
if int(line.strip().split()[0]) > 1:
1690
raise RuntimeError(line)
1691
except (ValueError, IndexError, RuntimeError) as error:
1692
logger.error("Unknown protocol version: %s", error)
1695
# Start GnuTLS connection
1698
except gnutls.errors.GNUTLSError as error:
1699
logger.warning("Handshake failed: %s", error)
1700
# Do not run session.bye() here: the session is not
1701
# established. Just abandon the request.
1703
logger.debug("Handshake succeeded")
1705
approval_required = False
1708
fpr = self.fingerprint(self.peer_certificate
1711
gnutls.errors.GNUTLSError) as error:
1712
logger.warning("Bad certificate: %s", error)
1714
logger.debug("Fingerprint: %s", fpr)
1717
client = ProxyClient(child_pipe, fpr,
1718
self.client_address)
1722
if client.approval_delay:
1723
delay = client.approval_delay
1724
client.approvals_pending += 1
1725
approval_required = True
1728
if not client.enabled:
1729
logger.info("Client %s is disabled",
1731
if self.server.use_dbus:
1733
client.Rejected("Disabled")
1736
if client.approved or not client.approval_delay:
1737
#We are approved or approval is disabled
1739
elif client.approved is None:
1740
logger.info("Client %s needs approval",
1742
if self.server.use_dbus:
1744
client.NeedApproval(
1745
client.approval_delay.total_seconds()
1746
* 1000, client.approved_by_default)
1748
logger.warning("Client %s was not approved",
1750
if self.server.use_dbus:
1752
client.Rejected("Denied")
1755
#wait until timeout or approved
1756
time = datetime.datetime.now()
1757
client.changedstate.acquire()
1758
client.changedstate.wait(delay.total_seconds())
1759
client.changedstate.release()
1760
time2 = datetime.datetime.now()
1761
if (time2 - time) >= delay:
1762
if not client.approved_by_default:
1763
logger.warning("Client %s timed out while"
1764
" waiting for approval",
1766
if self.server.use_dbus:
1768
client.Rejected("Approval timed out")
1773
delay -= time2 - time
1776
while sent_size < len(client.secret):
1778
sent = session.send(client.secret[sent_size:])
1779
except gnutls.errors.GNUTLSError as error:
1780
logger.warning("gnutls send failed",
1783
logger.debug("Sent: %d, remaining: %d",
1784
sent, len(client.secret)
1785
- (sent_size + sent))
1788
logger.info("Sending secret to %s", client.name)
1789
# bump the timeout using extended_timeout
1790
client.bump_timeout(client.extended_timeout)
1791
if self.server.use_dbus:
1796
if approval_required:
1797
client.approvals_pending -= 1
1800
except gnutls.errors.GNUTLSError as error:
1801
logger.warning("GnuTLS bye failed",
1805
def peer_certificate(session):
1806
"Return the peer's OpenPGP certificate as a bytestring"
1807
# If not an OpenPGP certificate...
1808
if (gnutls.library.functions
1809
.gnutls_certificate_type_get(session._c_object)
1810
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1811
# ...do the normal thing
1812
return session.peer_certificate
1813
list_size = ctypes.c_uint(1)
1814
cert_list = (gnutls.library.functions
1815
.gnutls_certificate_get_peers
1816
(session._c_object, ctypes.byref(list_size)))
1817
if not bool(cert_list) and list_size.value != 0:
1818
raise gnutls.errors.GNUTLSError("error getting peer"
1820
if list_size.value == 0:
1823
return ctypes.string_at(cert.data, cert.size)
1826
def fingerprint(openpgp):
1827
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1828
# New GnuTLS "datum" with the OpenPGP public key
1829
datum = (gnutls.library.types
1830
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1833
ctypes.c_uint(len(openpgp))))
1834
# New empty GnuTLS certificate
1835
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1836
(gnutls.library.functions
1837
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1838
# Import the OpenPGP public key into the certificate
1839
(gnutls.library.functions
1840
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1841
gnutls.library.constants
1842
.GNUTLS_OPENPGP_FMT_RAW))
1843
# Verify the self signature in the key
1844
crtverify = ctypes.c_uint()
1845
(gnutls.library.functions
1846
.gnutls_openpgp_crt_verify_self(crt, 0,
1847
ctypes.byref(crtverify)))
1848
if crtverify.value != 0:
1849
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1850
raise (gnutls.errors.CertificateSecurityError
1852
# New buffer for the fingerprint
1853
buf = ctypes.create_string_buffer(20)
1854
buf_len = ctypes.c_size_t()
1855
# Get the fingerprint from the certificate into the buffer
1856
(gnutls.library.functions
1857
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1858
ctypes.byref(buf_len)))
1859
# Deinit the certificate
1860
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1861
# Convert the buffer to a Python bytestring
1862
fpr = ctypes.string_at(buf, buf_len.value)
1863
# Convert the bytestring to hexadecimal notation
1864
hex_fpr = binascii.hexlify(fpr).upper()
1868
class MultiprocessingMixIn(object):
1869
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1870
def sub_process_main(self, request, address):
1872
self.finish_request(request, address)
1874
self.handle_error(request, address)
1875
self.close_request(request)
1877
def process_request(self, request, address):
1878
"""Start a new process to process the request."""
1879
proc = multiprocessing.Process(target = self.sub_process_main,
1880
args = (request, address))
1885
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1886
""" adds a pipe to the MixIn """
1887
def process_request(self, request, client_address):
1888
"""Overrides and wraps the original process_request().
1890
This function creates a new pipe in self.pipe
1892
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1894
proc = MultiprocessingMixIn.process_request(self, request,
1896
self.child_pipe.close()
1897
self.add_pipe(parent_pipe, proc)
1899
def add_pipe(self, parent_pipe, proc):
1900
"""Dummy function; override as necessary"""
1901
raise NotImplementedError()
1904
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1905
socketserver.TCPServer, object):
1906
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
657
logger.info(u"TCP connection from: %s",
658
unicode(self.client_address))
659
session = (gnutls.connection
660
.ClientSession(self.request,
664
line = self.request.makefile().readline()
665
logger.debug(u"Protocol version: %r", line)
667
if int(line.strip().split()[0]) > 1:
669
except (ValueError, IndexError, RuntimeError), error:
670
logger.error(u"Unknown protocol version: %s", error)
673
# Note: gnutls.connection.X509Credentials is really a generic
674
# GnuTLS certificate credentials object so long as no X.509
675
# keys are added to it. Therefore, we can use it here despite
676
# using OpenPGP certificates.
678
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
679
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
681
# Use a fallback default, since this MUST be set.
682
priority = self.server.settings.get("priority", "NORMAL")
683
(gnutls.library.functions
684
.gnutls_priority_set_direct(session._c_object,
689
except gnutls.errors.GNUTLSError, error:
690
logger.warning(u"Handshake failed: %s", error)
691
# Do not run session.bye() here: the session is not
692
# established. Just abandon the request.
694
logger.debug(u"Handshake succeeded")
696
fpr = fingerprint(peer_certificate(session))
697
except (TypeError, gnutls.errors.GNUTLSError), error:
698
logger.warning(u"Bad certificate: %s", error)
701
logger.debug(u"Fingerprint: %s", fpr)
703
for c in self.server.clients:
704
if c.fingerprint == fpr:
708
logger.warning(u"Client not found for fingerprint: %s",
712
# Have to check if client.still_valid(), since it is possible
713
# that the client timed out while establishing the GnuTLS
715
if not client.still_valid():
716
logger.warning(u"Client %(name)s is invalid",
720
## This won't work here, since we're in a fork.
721
# client.checked_ok()
723
while sent_size < len(client.secret):
724
sent = session.send(client.secret[sent_size:])
725
logger.debug(u"Sent: %d, remaining: %d",
726
sent, len(client.secret)
727
- (sent_size + sent))
732
class IPv6_TCPServer(SocketServer.ForkingMixIn,
733
SocketServer.TCPServer, object):
734
"""IPv6 TCP server. Accepts 'None' as address and/or port.
736
settings: Server settings
737
clients: Set() of Client objects
1909
738
enabled: Boolean; whether this server is activated yet
1910
interface: None or a network interface name (string)
1911
use_ipv6: Boolean; to use IPv6 or not
1913
def __init__(self, server_address, RequestHandlerClass,
1914
interface=None, use_ipv6=True, socketfd=None):
1915
"""If socketfd is set, use that file descriptor instead of
1916
creating a new one with socket.socket().
1918
self.interface = interface
1920
self.address_family = socket.AF_INET6
1921
if socketfd is not None:
1922
# Save the file descriptor
1923
self.socketfd = socketfd
1924
# Save the original socket.socket() function
1925
self.socket_socket = socket.socket
1926
# To implement --socket, we monkey patch socket.socket.
1928
# (When socketserver.TCPServer is a new-style class, we
1929
# could make self.socket into a property instead of monkey
1930
# patching socket.socket.)
1932
# Create a one-time-only replacement for socket.socket()
1933
@functools.wraps(socket.socket)
1934
def socket_wrapper(*args, **kwargs):
1935
# Restore original function so subsequent calls are
1937
socket.socket = self.socket_socket
1938
del self.socket_socket
1939
# This time only, return a new socket object from the
1940
# saved file descriptor.
1941
return socket.fromfd(self.socketfd, *args, **kwargs)
1942
# Replace socket.socket() function with wrapper
1943
socket.socket = socket_wrapper
1944
# The socketserver.TCPServer.__init__ will call
1945
# socket.socket(), which might be our replacement,
1946
# socket_wrapper(), if socketfd was set.
1947
socketserver.TCPServer.__init__(self, server_address,
1948
RequestHandlerClass)
740
address_family = socket.AF_INET6
741
def __init__(self, *args, **kwargs):
742
if "settings" in kwargs:
743
self.settings = kwargs["settings"]
744
del kwargs["settings"]
745
if "clients" in kwargs:
746
self.clients = kwargs["clients"]
747
del kwargs["clients"]
749
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1950
750
def server_bind(self):
1951
751
"""This overrides the normal server_bind() function
1952
752
to bind to an interface if one was specified, and also NOT to
1953
753
bind to an address or port if they were not specified."""
1954
if self.interface is not None:
1955
if SO_BINDTODEVICE is None:
1956
logger.error("SO_BINDTODEVICE does not exist;"
1957
" cannot bind to interface %s",
1961
self.socket.setsockopt(socket.SOL_SOCKET,
1963
str(self.interface + '\0'))
1964
except socket.error as error:
1965
if error.errno == errno.EPERM:
1966
logger.error("No permission to bind to"
1967
" interface %s", self.interface)
1968
elif error.errno == errno.ENOPROTOOPT:
1969
logger.error("SO_BINDTODEVICE not available;"
1970
" cannot bind to interface %s",
1972
elif error.errno == errno.ENODEV:
1973
logger.error("Interface %s does not exist,"
1974
" cannot bind", self.interface)
754
if self.settings["interface"]:
755
# 25 is from /usr/include/asm-i486/socket.h
756
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
758
self.socket.setsockopt(socket.SOL_SOCKET,
760
self.settings["interface"])
761
except socket.error, error:
762
if error[0] == errno.EPERM:
763
logger.error(u"No permission to"
764
u" bind to interface %s",
765
self.settings["interface"])
1977
768
# Only bind(2) the socket if we really need to.
1978
769
if self.server_address[0] or self.server_address[1]:
1979
770
if not self.server_address[0]:
1980
if self.address_family == socket.AF_INET6:
1981
any_address = "::" # in6addr_any
1983
any_address = "0.0.0.0" # INADDR_ANY
1984
self.server_address = (any_address,
772
self.server_address = (in6addr_any,
1985
773
self.server_address[1])
1986
774
elif not self.server_address[1]:
1987
775
self.server_address = (self.server_address[0],
1989
# if self.interface:
777
# if self.settings["interface"]:
1990
778
# self.server_address = (self.server_address[0],
1993
781
# if_nametoindex
1995
return socketserver.TCPServer.server_bind(self)
1998
class MandosServer(IPv6_TCPServer):
2002
clients: set of Client objects
2003
gnutls_priority GnuTLS priority string
2004
use_dbus: Boolean; to emit D-Bus signals or not
2006
Assumes a gobject.MainLoop event loop.
2008
def __init__(self, server_address, RequestHandlerClass,
2009
interface=None, use_ipv6=True, clients=None,
2010
gnutls_priority=None, use_dbus=True, socketfd=None):
2011
self.enabled = False
2012
self.clients = clients
2013
if self.clients is None:
2015
self.use_dbus = use_dbus
2016
self.gnutls_priority = gnutls_priority
2017
IPv6_TCPServer.__init__(self, server_address,
2018
RequestHandlerClass,
2019
interface = interface,
2020
use_ipv6 = use_ipv6,
2021
socketfd = socketfd)
784
return super(IPv6_TCPServer, self).server_bind()
2022
785
def server_activate(self):
2023
786
if self.enabled:
2024
return socketserver.TCPServer.server_activate(self)
787
return super(IPv6_TCPServer, self).server_activate()
2026
788
def enable(self):
2027
789
self.enabled = True
2029
def add_pipe(self, parent_pipe, proc):
2030
# Call "handle_ipc" for both data and EOF events
2031
gobject.io_add_watch(parent_pipe.fileno(),
2032
gobject.IO_IN | gobject.IO_HUP,
2033
functools.partial(self.handle_ipc,
2038
def handle_ipc(self, source, condition, parent_pipe=None,
2039
proc = None, client_object=None):
2040
# error, or the other end of multiprocessing.Pipe has closed
2041
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2042
# Wait for other process to exit
2046
# Read a request from the child
2047
request = parent_pipe.recv()
2048
command = request[0]
2050
if command == 'init':
2052
address = request[2]
2054
for c in self.clients.itervalues():
2055
if c.fingerprint == fpr:
2059
logger.info("Client not found for fingerprint: %s, ad"
2060
"dress: %s", fpr, address)
2063
mandos_dbus_service.ClientNotFound(fpr,
2065
parent_pipe.send(False)
2068
gobject.io_add_watch(parent_pipe.fileno(),
2069
gobject.IO_IN | gobject.IO_HUP,
2070
functools.partial(self.handle_ipc,
2076
parent_pipe.send(True)
2077
# remove the old hook in favor of the new above hook on
2080
if command == 'funcall':
2081
funcname = request[1]
2085
parent_pipe.send(('data', getattr(client_object,
2089
if command == 'getattr':
2090
attrname = request[1]
2091
if callable(client_object.__getattribute__(attrname)):
2092
parent_pipe.send(('function',))
2094
parent_pipe.send(('data', client_object
2095
.__getattribute__(attrname)))
2097
if command == 'setattr':
2098
attrname = request[1]
2100
setattr(client_object, attrname, value)
2105
def rfc3339_duration_to_delta(duration):
2106
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2108
>>> rfc3339_duration_to_delta("P7D")
2109
datetime.timedelta(7)
2110
>>> rfc3339_duration_to_delta("PT60S")
2111
datetime.timedelta(0, 60)
2112
>>> rfc3339_duration_to_delta("PT60M")
2113
datetime.timedelta(0, 3600)
2114
>>> rfc3339_duration_to_delta("PT24H")
2115
datetime.timedelta(1)
2116
>>> rfc3339_duration_to_delta("P1W")
2117
datetime.timedelta(7)
2118
>>> rfc3339_duration_to_delta("PT5M30S")
2119
datetime.timedelta(0, 330)
2120
>>> rfc3339_duration_to_delta("P1DT3M20S")
2121
datetime.timedelta(1, 200)
2124
# Parsing an RFC 3339 duration with regular expressions is not
2125
# possible - there would have to be multiple places for the same
2126
# values, like seconds. The current code, while more esoteric, is
2127
# cleaner without depending on a parsing library. If Python had a
2128
# built-in library for parsing we would use it, but we'd like to
2129
# avoid excessive use of external libraries.
2131
# New type for defining tokens, syntax, and semantics all-in-one
2132
Token = collections.namedtuple("Token",
2133
("regexp", # To match token; if
2134
# "value" is not None,
2135
# must have a "group"
2137
"value", # datetime.timedelta or
2139
"followers")) # Tokens valid after
2141
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2142
# the "duration" ABNF definition in RFC 3339, Appendix A.
2143
token_end = Token(re.compile(r"$"), None, frozenset())
2144
token_second = Token(re.compile(r"(\d+)S"),
2145
datetime.timedelta(seconds=1),
2146
frozenset((token_end,)))
2147
token_minute = Token(re.compile(r"(\d+)M"),
2148
datetime.timedelta(minutes=1),
2149
frozenset((token_second, token_end)))
2150
token_hour = Token(re.compile(r"(\d+)H"),
2151
datetime.timedelta(hours=1),
2152
frozenset((token_minute, token_end)))
2153
token_time = Token(re.compile(r"T"),
2155
frozenset((token_hour, token_minute,
2157
token_day = Token(re.compile(r"(\d+)D"),
2158
datetime.timedelta(days=1),
2159
frozenset((token_time, token_end)))
2160
token_month = Token(re.compile(r"(\d+)M"),
2161
datetime.timedelta(weeks=4),
2162
frozenset((token_day, token_end)))
2163
token_year = Token(re.compile(r"(\d+)Y"),
2164
datetime.timedelta(weeks=52),
2165
frozenset((token_month, token_end)))
2166
token_week = Token(re.compile(r"(\d+)W"),
2167
datetime.timedelta(weeks=1),
2168
frozenset((token_end,)))
2169
token_duration = Token(re.compile(r"P"), None,
2170
frozenset((token_year, token_month,
2171
token_day, token_time,
2173
# Define starting values
2174
value = datetime.timedelta() # Value so far
2176
followers = frozenset((token_duration,)) # Following valid tokens
2177
s = duration # String left to parse
2178
# Loop until end token is found
2179
while found_token is not token_end:
2180
# Search for any currently valid tokens
2181
for token in followers:
2182
match = token.regexp.match(s)
2183
if match is not None:
2185
if token.value is not None:
2186
# Value found, parse digits
2187
factor = int(match.group(1), 10)
2188
# Add to value so far
2189
value += factor * token.value
2190
# Strip token from string
2191
s = token.regexp.sub("", s, 1)
2194
# Set valid next tokens
2195
followers = found_token.followers
2198
# No currently valid tokens were found
2199
raise ValueError("Invalid RFC 3339 duration")
2204
792
def string_to_delta(interval):
2335
936
"debug": "False",
2337
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
938
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2338
939
"servicename": "Mandos",
2339
940
"use_dbus": "True",
2344
"statedir": "/var/lib/mandos",
2345
"foreground": "False",
2349
943
# Parse config file for server-global settings
2350
server_config = configparser.SafeConfigParser(server_defaults)
944
server_config = ConfigParser.SafeConfigParser(server_defaults)
2351
945
del server_defaults
2352
server_config.read(os.path.join(options.configdir,
946
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2354
947
# Convert the SafeConfigParser object to a dict
2355
948
server_settings = server_config.defaults()
2356
949
# Use the appropriate methods on the non-string config options
2357
for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2358
server_settings[option] = server_config.getboolean("DEFAULT",
950
server_settings["debug"] = server_config.getboolean("DEFAULT",
952
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
2360
954
if server_settings["port"]:
2361
955
server_settings["port"] = server_config.getint("DEFAULT",
2363
if server_settings["socket"]:
2364
server_settings["socket"] = server_config.getint("DEFAULT",
2366
# Later, stdin will, and stdout and stderr might, be dup'ed
2367
# over with an opened os.devnull. But we don't want this to
2368
# happen with a supplied network socket.
2369
if 0 <= server_settings["socket"] <= 2:
2370
server_settings["socket"] = os.dup(server_settings
2372
957
del server_config
2374
959
# Override the settings from the config file with command line
2375
960
# options, if set.
2376
961
for option in ("interface", "address", "port", "debug",
2377
962
"priority", "servicename", "configdir",
2378
"use_dbus", "use_ipv6", "debuglevel", "restore",
2379
"statedir", "socket", "foreground", "zeroconf"):
2380
964
value = getattr(options, option)
2381
965
if value is not None:
2382
966
server_settings[option] = value
2384
# Force all strings to be unicode
2385
for option in server_settings.keys():
2386
if type(server_settings[option]) is str:
2387
server_settings[option] = unicode(server_settings[option])
2388
# Force all boolean options to be boolean
2389
for option in ("debug", "use_dbus", "use_ipv6", "restore",
2390
"foreground", "zeroconf"):
2391
server_settings[option] = bool(server_settings[option])
2392
# Debug implies foreground
2393
if server_settings["debug"]:
2394
server_settings["foreground"] = True
2395
968
# Now we have our good server settings in "server_settings"
2397
##################################################################
2399
if (not server_settings["zeroconf"] and
2400
not (server_settings["port"]
2401
or server_settings["socket"] != "")):
2402
parser.error("Needs port or socket to work without"
2405
970
# For convenience
2406
971
debug = server_settings["debug"]
2407
debuglevel = server_settings["debuglevel"]
2408
972
use_dbus = server_settings["use_dbus"]
2409
use_ipv6 = server_settings["use_ipv6"]
2410
stored_state_path = os.path.join(server_settings["statedir"],
2412
foreground = server_settings["foreground"]
2413
zeroconf = server_settings["zeroconf"]
974
def sigsegvhandler(signum, frame):
975
raise RuntimeError('Segmentation fault')
2416
initlogger(debug, logging.DEBUG)
978
syslogger.setLevel(logging.WARNING)
979
console.setLevel(logging.WARNING)
2421
level = getattr(logging, debuglevel.upper())
2422
initlogger(debug, level)
981
signal.signal(signal.SIGSEGV, sigsegvhandler)
2424
983
if server_settings["servicename"] != "Mandos":
2425
984
syslogger.setFormatter(logging.Formatter
2426
('Mandos ({}) [%(process)d]:'
2427
' %(levelname)s: %(message)s'
2428
.format(server_settings
985
('Mandos (%s): %%(levelname)s:'
987
% server_settings["servicename"]))
2431
989
# Parse config file with clients
2432
client_config = configparser.SafeConfigParser(Client
990
client_defaults = { "timeout": "1h",
992
"checker": "fping -q -- %%(host)s",
995
client_config = ConfigParser.SafeConfigParser(client_defaults)
2434
996
client_config.read(os.path.join(server_settings["configdir"],
2435
997
"clients.conf"))
2437
global mandos_dbus_service
2438
mandos_dbus_service = None
2441
if server_settings["socket"] != "":
2442
socketfd = server_settings["socket"]
2443
tcp_server = MandosServer((server_settings["address"],
2444
server_settings["port"]),
2446
interface=(server_settings["interface"]
2450
server_settings["priority"],
2454
pidfilename = "/run/mandos.pid"
2455
if not os.path.isdir("/run/."):
2456
pidfilename = "/var/run/mandos.pid"
2459
pidfile = open(pidfilename, "w")
2460
except IOError as e:
2461
logger.error("Could not open file %r", pidfilename,
2464
for name in ("_mandos", "mandos", "nobody"):
2466
uid = pwd.getpwnam(name).pw_uid
2467
gid = pwd.getpwnam(name).pw_gid
1000
tcp_server = IPv6_TCPServer((server_settings["address"],
1001
server_settings["port"]),
1003
settings=server_settings,
1005
pidfilename = "/var/run/mandos.pid"
1007
pidfile = open(pidfilename, "w")
1008
except IOError, error:
1009
logger.error("Could not open file %r", pidfilename)
1012
uid = pwd.getpwnam("_mandos").pw_uid
1013
gid = pwd.getpwnam("_mandos").pw_gid
1016
uid = pwd.getpwnam("mandos").pw_uid
1017
gid = pwd.getpwnam("mandos").pw_gid
2469
1018
except KeyError:
1020
uid = pwd.getpwnam("nobody").pw_uid
1021
gid = pwd.getpwnam("nogroup").pw_gid
2477
except OSError as error:
2478
if error.errno != errno.EPERM:
1028
except OSError, error:
1029
if error[0] != errno.EPERM:
1032
# Enable all possible GnuTLS debugging
2482
# Enable all possible GnuTLS debugging
2484
1034
# "Use a log level over 10 to enable all debugging options."
2485
1035
# - GnuTLS manual
2486
1036
gnutls.library.functions.gnutls_global_set_log_level(11)
2492
1042
(gnutls.library.functions
2493
1043
.gnutls_global_set_log_function(debug_gnutls))
2495
# Redirect stdin so all checkers get /dev/null
2496
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2497
os.dup2(null, sys.stdin.fileno())
2501
# Need to fork before connecting to D-Bus
2503
# Close all input and output, do double fork, etc.
2506
# multiprocessing will use threads, so before we use gobject we
2507
# need to inform gobject that threads will be used.
2508
gobject.threads_init()
1046
service = AvahiService(name = server_settings["servicename"],
1047
servicetype = "_mandos._tcp", )
1048
if server_settings["interface"]:
1049
service.interface = (if_nametoindex
1050
(server_settings["interface"]))
2510
1052
global main_loop
2511
1055
# From the Avahi example code
2512
DBusGMainLoop(set_as_default=True)
1056
DBusGMainLoop(set_as_default=True )
2513
1057
main_loop = gobject.MainLoop()
2514
1058
bus = dbus.SystemBus()
1059
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1060
avahi.DBUS_PATH_SERVER),
1061
avahi.DBUS_INTERFACE_SERVER)
2515
1062
# End of Avahi example code
2518
bus_name = dbus.service.BusName("se.recompile.Mandos",
2519
bus, do_not_queue=True)
2520
old_bus_name = (dbus.service.BusName
2521
("se.bsnet.fukt.Mandos", bus,
2523
except dbus.exceptions.NameExistsException as e:
2524
logger.error("Disabling D-Bus:", exc_info=e)
2526
server_settings["use_dbus"] = False
2527
tcp_server.use_dbus = False
2529
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2530
service = AvahiServiceToSyslog(name =
2531
server_settings["servicename"],
2532
servicetype = "_mandos._tcp",
2533
protocol = protocol, bus = bus)
2534
if server_settings["interface"]:
2535
service.interface = (if_nametoindex
2536
(str(server_settings["interface"])))
2538
global multiprocessing_manager
2539
multiprocessing_manager = multiprocessing.Manager()
2541
client_class = Client
2543
client_class = functools.partial(ClientDBus, bus = bus)
2545
client_settings = Client.config_parser(client_config)
2546
old_client_settings = {}
2549
# This is used to redirect stdout and stderr for checker processes
2551
wnull = open(os.devnull, "w") # A writable /dev/null
2552
# Only used if server is running in foreground but not in debug
2554
if debug or not foreground:
2557
# Get client data and settings from last running state.
2558
if server_settings["restore"]:
2560
with open(stored_state_path, "rb") as stored_state:
2561
clients_data, old_client_settings = (pickle.load
2563
os.remove(stored_state_path)
2564
except IOError as e:
2565
if e.errno == errno.ENOENT:
2566
logger.warning("Could not load persistent state: {}"
2567
.format(os.strerror(e.errno)))
2569
logger.critical("Could not load persistent state:",
2572
except EOFError as e:
2573
logger.warning("Could not load persistent state: "
2574
"EOFError:", exc_info=e)
2576
with PGPEngine() as pgp:
2577
for client_name, client in clients_data.items():
2578
# Skip removed clients
2579
if client_name not in client_settings:
2582
# Decide which value to use after restoring saved state.
2583
# We have three different values: Old config file,
2584
# new config file, and saved state.
2585
# New config value takes precedence if it differs from old
2586
# config value, otherwise use saved state.
2587
for name, value in client_settings[client_name].items():
2589
# For each value in new config, check if it
2590
# differs from the old config value (Except for
2591
# the "secret" attribute)
2592
if (name != "secret" and
2593
value != old_client_settings[client_name]
2595
client[name] = value
2599
# Clients who has passed its expire date can still be
2600
# enabled if its last checker was successful. Clients
2601
# whose checker succeeded before we stored its state is
2602
# assumed to have successfully run all checkers during
2604
if client["enabled"]:
2605
if datetime.datetime.utcnow() >= client["expires"]:
2606
if not client["last_checked_ok"]:
2608
"disabling client {} - Client never "
2609
"performed a successful checker"
2610
.format(client_name))
2611
client["enabled"] = False
2612
elif client["last_checker_status"] != 0:
2614
"disabling client {} - Client last"
2615
" checker failed with error code {}"
2616
.format(client_name,
2617
client["last_checker_status"]))
2618
client["enabled"] = False
2620
client["expires"] = (datetime.datetime
2622
+ client["timeout"])
2623
logger.debug("Last checker succeeded,"
2624
" keeping {} enabled"
2625
.format(client_name))
2627
client["secret"] = (
2628
pgp.decrypt(client["encrypted_secret"],
2629
client_settings[client_name]
2632
# If decryption fails, we use secret from new settings
2633
logger.debug("Failed to decrypt {} old secret"
2634
.format(client_name))
2635
client["secret"] = (
2636
client_settings[client_name]["secret"])
2638
# Add/remove clients based on new changes made to config
2639
for client_name in (set(old_client_settings)
2640
- set(client_settings)):
2641
del clients_data[client_name]
2642
for client_name in (set(client_settings)
2643
- set(old_client_settings)):
2644
clients_data[client_name] = client_settings[client_name]
2646
# Create all client objects
2647
for client_name, client in clients_data.items():
2648
tcp_server.clients[client_name] = client_class(
2649
name = client_name, settings = client,
2650
server_settings = server_settings)
2652
if not tcp_server.clients:
2653
logger.warning("No clients defined")
2656
if pidfile is not None:
2660
pidfile.write(str(pid) + "\n".encode("utf-8"))
2662
logger.error("Could not write to file %r with PID %d",
1064
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1066
clients.update(Set(Client(name = section,
1068
= dict(client_config.items(section)),
1069
use_dbus = use_dbus)
1070
for section in client_config.sections()))
1072
logger.warning(u"No clients defined")
1075
# Redirect stdin so all checkers get /dev/null
1076
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1077
os.dup2(null, sys.stdin.fileno())
1081
# No console logging
1082
logger.removeHandler(console)
1083
# Close all input and output, do double fork, etc.
1088
pidfile.write(str(pid) + "\n")
1092
logger.error(u"Could not write to file %r with PID %d",
1095
# "pidfile" was never created
1100
"Cleanup function; run on exit"
1102
# From the Avahi example code
1103
if not group is None:
1106
# End of Avahi example code
1109
client = clients.pop()
1110
client.disable_hook = None
1113
atexit.register(cleanup)
1116
signal.signal(signal.SIGINT, signal.SIG_IGN)
2667
1117
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2668
1118
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2671
@alternate_dbus_interfaces({"se.recompile.Mandos":
2672
"se.bsnet.fukt.Mandos"})
2673
class MandosDBusService(DBusObjectWithProperties):
1121
class MandosServer(dbus.service.Object):
2674
1122
"""A D-Bus proxy object"""
2675
1123
def __init__(self):
2676
1124
dbus.service.Object.__init__(self, bus, "/")
2677
_interface = "se.recompile.Mandos"
2679
@dbus_interface_annotations(_interface)
2681
return { "org.freedesktop.DBus.Property"
2682
".EmitsChangedSignal":
2685
@dbus.service.signal(_interface, signature="o")
2686
def ClientAdded(self, objpath):
2690
@dbus.service.signal(_interface, signature="ss")
2691
def ClientNotFound(self, fingerprint, address):
1125
_interface = u"se.bsnet.fukt.Mandos"
1127
@dbus.service.signal(_interface, signature="oa{sv}")
1128
def ClientAdded(self, objpath, properties):