241
107
max_renames: integer; maximum number of renames
242
108
rename_count: integer; counter so we only rename after collisions
243
109
a sensible number of times
244
group: D-Bus Entry Group
246
bus: dbus.SystemBus()
249
111
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
250
servicetype = None, port = None, TXT = None,
251
domain = "", host = "", max_renames = 32768,
252
protocol = avahi.PROTO_UNSPEC, bus = None):
112
servicetype = None, port = None, TXT = None, domain = "",
113
host = "", max_renames = 32768):
253
114
self.interface = interface
255
116
self.type = servicetype
257
self.TXT = TXT if TXT is not None else []
258
122
self.domain = domain
260
124
self.rename_count = 0
261
125
self.max_renames = max_renames
262
self.protocol = protocol
263
self.group = None # our entry group
266
self.entry_group_state_changed_match = None
268
126
def rename(self):
269
127
"""Derived from the Avahi example code"""
270
128
if self.rename_count >= self.max_renames:
271
logger.critical("No suitable Zeroconf service name found"
272
" after %i retries, exiting.",
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
273
131
self.rename_count)
274
132
raise AvahiServiceError("Too many renames")
275
self.name = unicode(self.server
276
.GetAlternativeServiceName(self.name))
277
logger.info("Changing Zeroconf service name to %r ...",
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))
282
except dbus.exceptions.DBusException as error:
283
logger.critical("D-Bus Exception", exc_info=error)
286
141
self.rename_count += 1
288
142
def remove(self):
289
143
"""Derived from the Avahi example code"""
290
if self.entry_group_state_changed_match is not None:
291
self.entry_group_state_changed_match.remove()
292
self.entry_group_state_changed_match = None
293
if self.group is not None:
144
if group is not None:
297
147
"""Derived from the Avahi example code"""
299
if self.group is None:
300
self.group = dbus.Interface(
301
self.bus.get_object(avahi.DBUS_NAME,
302
self.server.EntryGroupNew()),
303
avahi.DBUS_INTERFACE_ENTRY_GROUP)
304
self.entry_group_state_changed_match = (
305
self.group.connect_to_signal(
306
'StateChanged', self.entry_group_state_changed))
307
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
308
self.name, self.type)
309
self.group.AddService(
312
dbus.UInt32(0), # flags
313
self.name, self.type,
314
self.domain, self.host,
315
dbus.UInt16(self.port),
316
avahi.string_array_to_txt_array(self.TXT))
319
def entry_group_state_changed(self, state, error):
320
"""Derived from the Avahi example code"""
321
logger.debug("Avahi entry group state change: %i", state)
323
if state == avahi.ENTRY_GROUP_ESTABLISHED:
324
logger.debug("Zeroconf service established.")
325
elif state == avahi.ENTRY_GROUP_COLLISION:
326
logger.info("Zeroconf service name collision.")
328
elif state == avahi.ENTRY_GROUP_FAILURE:
329
logger.critical("Avahi: Error in group state changed %s",
331
raise AvahiGroupError("State changed: {0!s}"
335
"""Derived from the Avahi example code"""
336
if self.group is not None:
339
except (dbus.exceptions.UnknownMethodException,
340
dbus.exceptions.DBusException):
345
def server_state_changed(self, state, error=None):
346
"""Derived from the Avahi example code"""
347
logger.debug("Avahi server state change: %i", state)
348
bad_states = { avahi.SERVER_INVALID:
349
"Zeroconf server invalid",
350
avahi.SERVER_REGISTERING: None,
351
avahi.SERVER_COLLISION:
352
"Zeroconf server name collision",
353
avahi.SERVER_FAILURE:
354
"Zeroconf server failure" }
355
if state in bad_states:
356
if bad_states[state] is not None:
358
logger.error(bad_states[state])
360
logger.error(bad_states[state] + ": %r", error)
362
elif state == avahi.SERVER_RUNNING:
366
logger.debug("Unknown state: %r", state)
368
logger.debug("Unknown state: %r: %r", state, error)
371
"""Derived from the Avahi example code"""
372
if self.server is None:
373
self.server = dbus.Interface(
374
self.bus.get_object(avahi.DBUS_NAME,
375
avahi.DBUS_PATH_SERVER,
376
follow_name_owner_changes=True),
377
avahi.DBUS_INTERFACE_SERVER)
378
self.server.connect_to_signal("StateChanged",
379
self.server_state_changed)
380
self.server_state_changed(self.server.GetState())
382
class AvahiServiceToSyslog(AvahiService):
384
"""Add the new name to the syslog messages"""
385
ret = AvahiService.rename(self)
386
syslogger.setFormatter(logging.Formatter
387
('Mandos ({0}) [%(process)d]:'
388
' %(levelname)s: %(message)s'
392
def timedelta_to_milliseconds(td):
393
"Convert a datetime.timedelta() to milliseconds"
394
return ((td.days * 24 * 60 * 60 * 1000)
395
+ (td.seconds * 1000)
396
+ (td.microseconds // 1000))
150
group = dbus.Interface\
151
(bus.get_object(avahi.DBUS_NAME,
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
398
173
class Client(object):
399
174
"""A representation of a client host served by this server.
402
approved: bool(); 'None' if not yet approved/disapproved
403
approval_delay: datetime.timedelta(); Time to wait for approval
404
approval_duration: datetime.timedelta(); Duration of one approval
405
checker: subprocess.Popen(); a running checker process used
406
to see if the client lives.
407
'None' if no process is running.
408
checker_callback_tag: a gobject event source tag, or None
409
checker_command: string; External command which is run to check
410
if client lives. %() expansions are done at
176
name: string; from the config file, used in log messages
177
fingerprint: string (40 or 32 hexadecimal digits); used to
178
uniquely identify the client
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); object creation, not client host
182
last_checked_ok: datetime.datetime() or None if not yet checked OK
183
timeout: datetime.timedelta(); How long from last_checked_ok
184
until this client is invalid
185
interval: datetime.timedelta(); How often to start a new checker
186
stop_hook: If set, called by stop() as stop_hook(self)
187
checker: subprocess.Popen(); a running checker process used
188
to see if the client lives.
189
'None' if no process is running.
190
checker_initiator_tag: a gobject event source tag, or None
191
stop_initiator_tag: - '' -
192
checker_callback_tag: - '' -
193
checker_command: string; External command which is run to check if
194
client lives. %() expansions are done at
411
195
runtime with vars(self) as dict, so that for
412
196
instance %(name)s can be used in the command.
413
checker_initiator_tag: a gobject event source tag, or None
414
created: datetime.datetime(); (UTC) object creation
415
client_structure: Object describing what attributes a client has
416
and is used for storing the client at exit
417
current_checker_command: string; current running checker_command
418
disable_initiator_tag: a gobject event source tag, or None
420
fingerprint: string (40 or 32 hexadecimal digits); used to
421
uniquely identify the client
422
host: string; available for use by the checker command
423
interval: datetime.timedelta(); How often to start a new checker
424
last_approval_request: datetime.datetime(); (UTC) or None
425
last_checked_ok: datetime.datetime(); (UTC) or None
426
last_checker_status: integer between 0 and 255 reflecting exit
427
status of last checker. -1 reflects crashed
428
checker, -2 means no checker completed yet.
429
last_enabled: datetime.datetime(); (UTC) or None
430
name: string; from the config file, used in log messages and
432
secret: bytestring; sent verbatim (over TLS) to client
433
timeout: datetime.timedelta(); How long from last_checked_ok
434
until this client is disabled
435
extended_timeout: extra long timeout when secret has been sent
436
runtime_expansions: Allowed attributes for runtime expansion.
437
expires: datetime.datetime(); time (UTC) when a client will be
198
_timeout: Real variable for 'timeout'
199
_interval: Real variable for 'interval'
200
_timeout_milliseconds: Used when calling gobject.timeout_add()
201
_interval_milliseconds: - '' -
441
runtime_expansions = ("approval_delay", "approval_duration",
442
"created", "enabled", "fingerprint",
443
"host", "interval", "last_checked_ok",
444
"last_enabled", "name", "timeout")
445
client_defaults = { "timeout": "5m",
446
"extended_timeout": "15m",
448
"checker": "fping -q -- %%(host)s",
450
"approval_delay": "0s",
451
"approval_duration": "1s",
452
"approved_by_default": "True",
456
def timeout_milliseconds(self):
457
"Return the 'timeout' attribute in milliseconds"
458
return timedelta_to_milliseconds(self.timeout)
460
def extended_timeout_milliseconds(self):
461
"Return the 'extended_timeout' attribute in milliseconds"
462
return timedelta_to_milliseconds(self.extended_timeout)
464
def interval_milliseconds(self):
465
"Return the 'interval' attribute in milliseconds"
466
return timedelta_to_milliseconds(self.interval)
468
def approval_delay_milliseconds(self):
469
return timedelta_to_milliseconds(self.approval_delay)
472
def config_parser(config):
473
"""Construct a new dict of client settings of this form:
474
{ client_name: {setting_name: value, ...}, ...}
475
with exceptions for any special settings as defined above.
476
NOTE: Must be a pure function. Must return the same result
477
value given the same arguments.
480
for client_name in config.sections():
481
section = dict(config.items(client_name))
482
client = settings[client_name] = {}
484
client["host"] = section["host"]
485
# Reformat values from string types to Python types
486
client["approved_by_default"] = config.getboolean(
487
client_name, "approved_by_default")
488
client["enabled"] = config.getboolean(client_name,
491
client["fingerprint"] = (section["fingerprint"].upper()
493
if "secret" in section:
494
client["secret"] = section["secret"].decode("base64")
495
elif "secfile" in section:
496
with open(os.path.expanduser(os.path.expandvars
497
(section["secfile"])),
499
client["secret"] = secfile.read()
501
raise TypeError("No secret or secfile for section {0}"
503
client["timeout"] = string_to_delta(section["timeout"])
504
client["extended_timeout"] = string_to_delta(
505
section["extended_timeout"])
506
client["interval"] = string_to_delta(section["interval"])
507
client["approval_delay"] = string_to_delta(
508
section["approval_delay"])
509
client["approval_duration"] = string_to_delta(
510
section["approval_duration"])
511
client["checker_command"] = section["checker"]
512
client["last_approval_request"] = None
513
client["last_checked_ok"] = None
514
client["last_checker_status"] = -2
518
def __init__(self, settings, name = None):
203
def _set_timeout(self, timeout):
204
"Setter function for 'timeout' attribute"
205
self._timeout = timeout
206
self._timeout_milliseconds = ((self.timeout.days
207
* 24 * 60 * 60 * 1000)
208
+ (self.timeout.seconds * 1000)
209
+ (self.timeout.microseconds
211
timeout = property(lambda self: self._timeout,
214
def _set_interval(self, interval):
215
"Setter function for 'interval' attribute"
216
self._interval = interval
217
self._interval_milliseconds = ((self.interval.days
218
* 24 * 60 * 60 * 1000)
219
+ (self.interval.seconds
221
+ (self.interval.microseconds
223
interval = property(lambda self: self._interval,
226
def __init__(self, name = None, stop_hook=None, config=None):
227
"""Note: the 'checker' key in 'config' sets the
228
'checker_command' attribute and *not* the 'checker'
520
# adding all client settings
521
for setting, value in settings.iteritems():
522
setattr(self, setting, value)
525
if not hasattr(self, "last_enabled"):
526
self.last_enabled = datetime.datetime.utcnow()
527
if not hasattr(self, "expires"):
528
self.expires = (datetime.datetime.utcnow()
531
self.last_enabled = None
534
logger.debug("Creating client %r", self.name)
233
logger.debug(u"Creating client %r", self.name)
535
234
# Uppercase and remove spaces from fingerprint for later
536
235
# comparison purposes with return value from the fingerprint()
538
logger.debug(" Fingerprint: %s", self.fingerprint)
539
self.created = settings.get("created",
540
datetime.datetime.utcnow())
542
# attributes specific for this server instance
237
self.fingerprint = config["fingerprint"].upper()\
239
logger.debug(u" Fingerprint: %s", self.fingerprint)
240
if "secret" in config:
241
self.secret = config["secret"].decode(u"base64")
242
elif "secfile" in config:
243
secfile = open(os.path.expanduser(os.path.expandvars
244
(config["secfile"])))
245
self.secret = secfile.read()
248
raise TypeError(u"No secret or secfile for client %s"
250
self.host = config.get("host", "")
251
self.created = datetime.datetime.now()
252
self.last_checked_ok = None
253
self.timeout = string_to_delta(config["timeout"])
254
self.interval = string_to_delta(config["interval"])
255
self.stop_hook = stop_hook
543
256
self.checker = None
544
257
self.checker_initiator_tag = None
545
self.disable_initiator_tag = None
258
self.stop_initiator_tag = None
546
259
self.checker_callback_tag = None
547
self.current_checker_command = None
549
self.approvals_pending = 0
550
self.changedstate = (multiprocessing_manager
551
.Condition(multiprocessing_manager
553
self.client_structure = [attr for attr in
554
self.__dict__.iterkeys()
555
if not attr.startswith("_")]
556
self.client_structure.append("client_structure")
558
for name, t in inspect.getmembers(type(self),
562
if not name.startswith("_"):
563
self.client_structure.append(name)
565
# Send notice to process children that client state has changed
566
def send_changedstate(self):
567
with self.changedstate:
568
self.changedstate.notify_all()
260
self.check_command = config["checker"]
571
262
"""Start this client's checker and timeout hooks"""
572
if getattr(self, "enabled", False):
575
self.expires = datetime.datetime.utcnow() + self.timeout
577
self.last_enabled = datetime.datetime.utcnow()
579
self.send_changedstate()
581
def disable(self, quiet=True):
582
"""Disable this client."""
583
if not getattr(self, "enabled", False):
586
logger.info("Disabling client %s", self.name)
587
if getattr(self, "disable_initiator_tag", None) is not None:
588
gobject.source_remove(self.disable_initiator_tag)
589
self.disable_initiator_tag = None
591
if getattr(self, "checker_initiator_tag", None) is not None:
592
gobject.source_remove(self.checker_initiator_tag)
593
self.checker_initiator_tag = None
597
self.send_changedstate()
598
# Do not run this again if called by a gobject.timeout_add
604
def init_checker(self):
605
263
# Schedule a new checker to be started an 'interval' from now,
606
264
# and every interval from then on.
607
if self.checker_initiator_tag is not None:
608
gobject.source_remove(self.checker_initiator_tag)
609
self.checker_initiator_tag = (gobject.timeout_add
610
(self.interval_milliseconds(),
612
# Schedule a disable() when 'timeout' has passed
613
if self.disable_initiator_tag is not None:
614
gobject.source_remove(self.disable_initiator_tag)
615
self.disable_initiator_tag = (gobject.timeout_add
616
(self.timeout_milliseconds(),
265
self.checker_initiator_tag = gobject.timeout_add\
266
(self._interval_milliseconds,
618
268
# Also start a new checker *right now*.
619
269
self.start_checker()
621
def checker_callback(self, pid, condition, command):
270
# Schedule a stop() when 'timeout' has passed
271
self.stop_initiator_tag = gobject.timeout_add\
272
(self._timeout_milliseconds,
276
The possibility that a client might be restarted is left open,
277
but not currently used."""
278
# If this client doesn't have a secret, it is already stopped.
279
if hasattr(self, "secret") and self.secret:
280
logger.info(u"Stopping client %s", self.name)
284
if getattr(self, "stop_initiator_tag", False):
285
gobject.source_remove(self.stop_initiator_tag)
286
self.stop_initiator_tag = None
287
if getattr(self, "checker_initiator_tag", False):
288
gobject.source_remove(self.checker_initiator_tag)
289
self.checker_initiator_tag = None
293
# Do not run this again if called by a gobject.timeout_add
296
self.stop_hook = None
298
def checker_callback(self, pid, condition):
622
299
"""The checker has completed, so take appropriate actions."""
300
now = datetime.datetime.now()
623
301
self.checker_callback_tag = None
624
302
self.checker = None
625
if os.WIFEXITED(condition):
626
self.last_checker_status = os.WEXITSTATUS(condition)
627
if self.last_checker_status == 0:
628
logger.info("Checker for %(name)s succeeded",
632
logger.info("Checker for %(name)s failed",
635
self.last_checker_status = -1
636
logger.warning("Checker for %(name)s crashed?",
303
if os.WIFEXITED(condition) \
304
and (os.WEXITSTATUS(condition) == 0):
305
logger.info(u"Checker for %(name)s succeeded",
307
self.last_checked_ok = now
308
gobject.source_remove(self.stop_initiator_tag)
309
self.stop_initiator_tag = gobject.timeout_add\
310
(self._timeout_milliseconds,
312
elif not os.WIFEXITED(condition):
313
logger.warning(u"Checker for %(name)s crashed?",
639
def checked_ok(self):
640
"""Assert that the client has been seen, alive and well."""
641
self.last_checked_ok = datetime.datetime.utcnow()
642
self.last_checker_status = 0
645
def bump_timeout(self, timeout=None):
646
"""Bump up the timeout for this client."""
648
timeout = self.timeout
649
if self.disable_initiator_tag is not None:
650
gobject.source_remove(self.disable_initiator_tag)
651
self.disable_initiator_tag = None
652
if getattr(self, "enabled", False):
653
self.disable_initiator_tag = (gobject.timeout_add
654
(timedelta_to_milliseconds
655
(timeout), self.disable))
656
self.expires = datetime.datetime.utcnow() + timeout
658
def need_approval(self):
659
self.last_approval_request = datetime.datetime.utcnow()
316
logger.info(u"Checker for %(name)s failed",
661
318
def start_checker(self):
662
319
"""Start a new checker subprocess if one is not running.
664
320
If a checker already exists, leave it running and do
666
322
# The reason for not killing a running checker is that if we
667
# did that, and if a checker (for some reason) started running
668
# slowly and taking more than 'interval' time, then the client
669
# would inevitably timeout, since no checker would get a
670
# chance to run to completion. If we instead leave running
323
# did that, then if a checker (for some reason) started
324
# running slowly and taking more than 'interval' time, the
325
# client would inevitably timeout, since no checker would get
326
# a chance to run to completion. If we instead leave running
671
327
# checkers alone, the checker would have to take more time
672
# than 'timeout' for the client to be disabled, which is as it
675
# If a checker exists, make sure it is not a zombie
677
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
678
except (AttributeError, OSError) as error:
679
if (isinstance(error, OSError)
680
and error.errno != errno.ECHILD):
684
logger.warning("Checker was a zombie")
685
gobject.source_remove(self.checker_callback_tag)
686
self.checker_callback(pid, status,
687
self.current_checker_command)
688
# Start a new checker if needed
328
# than 'timeout' for the client to be declared invalid, which
329
# is as it should be.
689
330
if self.checker is None:
691
# In case checker_command has exactly one % operator
692
command = self.checker_command % self.host
332
# In case check_command has exactly one % operator
333
command = self.check_command % self.host
693
334
except TypeError:
694
335
# Escape attributes for the shell
695
escaped_attrs = dict(
697
re.escape(unicode(str(getattr(self, attr, "")),
701
self.runtime_expansions)
336
escaped_attrs = dict((key, re.escape(str(val)))
338
vars(self).iteritems())
704
command = self.checker_command % escaped_attrs
705
except TypeError as error:
706
logger.error('Could not format string "%s"',
707
self.checker_command, exc_info=error)
340
command = self.check_command % escaped_attrs
341
except TypeError, error:
342
logger.error(u'Could not format string "%s":'
343
u' %s', self.check_command, error)
708
344
return True # Try again later
709
self.current_checker_command = command
711
logger.info("Starting checker %r for %s",
346
logger.info(u"Starting checker %r for %s",
712
347
command, self.name)
713
348
# We don't need to redirect stdout and stderr, since
714
349
# in normal mode, that is already done by daemon(),
740
367
self.checker_callback_tag = None
741
368
if getattr(self, "checker", None) is None:
743
logger.debug("Stopping checker for %(name)s", vars(self))
370
logger.debug(u"Stopping checker for %(name)s", vars(self))
745
self.checker.terminate()
372
os.kill(self.checker.pid, signal.SIGTERM)
747
374
#if self.checker.poll() is None:
748
# self.checker.kill()
749
except OSError as error:
375
# os.kill(self.checker.pid, signal.SIGKILL)
376
except OSError, error:
750
377
if error.errno != errno.ESRCH: # No such process
752
379
self.checker = None
755
def dbus_service_property(dbus_interface, signature="v",
756
access="readwrite", byte_arrays=False):
757
"""Decorators for marking methods of a DBusObjectWithProperties to
758
become properties on the D-Bus.
760
The decorated method will be called with no arguments by "Get"
761
and with one argument by "Set".
763
The parameters, where they are supported, are the same as
764
dbus.service.method, except there is only "signature", since the
765
type from Get() and the type sent to Set() is the same.
767
# Encoding deeply encoded byte arrays is not supported yet by the
768
# "Set" method, so we fail early here:
769
if byte_arrays and signature != "ay":
770
raise ValueError("Byte arrays not supported for non-'ay'"
771
" signature {0!r}".format(signature))
773
func._dbus_is_property = True
774
func._dbus_interface = dbus_interface
775
func._dbus_signature = signature
776
func._dbus_access = access
777
func._dbus_name = func.__name__
778
if func._dbus_name.endswith("_dbus_property"):
779
func._dbus_name = func._dbus_name[:-14]
780
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
785
def dbus_interface_annotations(dbus_interface):
786
"""Decorator for marking functions returning interface annotations
790
@dbus_interface_annotations("org.example.Interface")
791
def _foo(self): # Function name does not matter
792
return {"org.freedesktop.DBus.Deprecated": "true",
793
"org.freedesktop.DBus.Property.EmitsChangedSignal":
797
func._dbus_is_interface = True
798
func._dbus_interface = dbus_interface
799
func._dbus_name = dbus_interface
804
def dbus_annotations(annotations):
805
"""Decorator to annotate D-Bus methods, signals or properties
808
@dbus_service_property("org.example.Interface", signature="b",
810
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
811
"org.freedesktop.DBus.Property."
812
"EmitsChangedSignal": "false"})
813
def Property_dbus_property(self):
814
return dbus.Boolean(False)
817
func._dbus_annotations = annotations
822
class DBusPropertyException(dbus.exceptions.DBusException):
823
"""A base class for D-Bus property-related exceptions
825
def __unicode__(self):
826
return unicode(str(self))
829
class DBusPropertyAccessException(DBusPropertyException):
830
"""A property's access permissions disallows an operation.
835
class DBusPropertyNotFound(DBusPropertyException):
836
"""An attempt was made to access a non-existing property.
841
class DBusObjectWithProperties(dbus.service.Object):
842
"""A D-Bus object with properties.
844
Classes inheriting from this can use the dbus_service_property
845
decorator to expose methods as D-Bus properties. It exposes the
846
standard Get(), Set(), and GetAll() methods on the D-Bus.
850
def _is_dbus_thing(thing):
851
"""Returns a function testing if an attribute is a D-Bus thing
853
If called like _is_dbus_thing("method") it returns a function
854
suitable for use as predicate to inspect.getmembers().
856
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
859
def _get_all_dbus_things(self, thing):
860
"""Returns a generator of (name, attribute) pairs
862
return ((getattr(athing.__get__(self), "_dbus_name",
864
athing.__get__(self))
865
for cls in self.__class__.__mro__
867
inspect.getmembers(cls,
868
self._is_dbus_thing(thing)))
870
def _get_dbus_property(self, interface_name, property_name):
871
"""Returns a bound method if one exists which is a D-Bus
872
property with the specified name and interface.
874
for cls in self.__class__.__mro__:
875
for name, value in (inspect.getmembers
877
self._is_dbus_thing("property"))):
878
if (value._dbus_name == property_name
879
and value._dbus_interface == interface_name):
880
return value.__get__(self)
883
raise DBusPropertyNotFound(self.dbus_object_path + ":"
884
+ interface_name + "."
887
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
889
def Get(self, interface_name, property_name):
890
"""Standard D-Bus property Get() method, see D-Bus standard.
892
prop = self._get_dbus_property(interface_name, property_name)
893
if prop._dbus_access == "write":
894
raise DBusPropertyAccessException(property_name)
896
if not hasattr(value, "variant_level"):
898
return type(value)(value, variant_level=value.variant_level+1)
900
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
901
def Set(self, interface_name, property_name, value):
902
"""Standard D-Bus property Set() method, see D-Bus standard.
904
prop = self._get_dbus_property(interface_name, property_name)
905
if prop._dbus_access == "read":
906
raise DBusPropertyAccessException(property_name)
907
if prop._dbus_get_args_options["byte_arrays"]:
908
# The byte_arrays option is not supported yet on
909
# signatures other than "ay".
910
if prop._dbus_signature != "ay":
912
value = dbus.ByteArray(b''.join(chr(byte)
916
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
917
out_signature="a{sv}")
918
def GetAll(self, interface_name):
919
"""Standard D-Bus property GetAll() method, see D-Bus
922
Note: Will not include properties with access="write".
925
for name, prop in self._get_all_dbus_things("property"):
927
and interface_name != prop._dbus_interface):
928
# Interface non-empty but did not match
930
# Ignore write-only properties
931
if prop._dbus_access == "write":
934
if not hasattr(value, "variant_level"):
935
properties[name] = value
937
properties[name] = type(value)(value, variant_level=
938
value.variant_level+1)
939
return dbus.Dictionary(properties, signature="sv")
941
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
943
path_keyword='object_path',
944
connection_keyword='connection')
945
def Introspect(self, object_path, connection):
946
"""Overloading of standard D-Bus method.
948
Inserts property tags and interface annotation tags.
950
xmlstring = dbus.service.Object.Introspect(self, object_path,
953
document = xml.dom.minidom.parseString(xmlstring)
954
def make_tag(document, name, prop):
955
e = document.createElement("property")
956
e.setAttribute("name", name)
957
e.setAttribute("type", prop._dbus_signature)
958
e.setAttribute("access", prop._dbus_access)
960
for if_tag in document.getElementsByTagName("interface"):
962
for tag in (make_tag(document, name, prop)
964
in self._get_all_dbus_things("property")
965
if prop._dbus_interface
966
== if_tag.getAttribute("name")):
967
if_tag.appendChild(tag)
968
# Add annotation tags
969
for typ in ("method", "signal", "property"):
970
for tag in if_tag.getElementsByTagName(typ):
972
for name, prop in (self.
973
_get_all_dbus_things(typ)):
974
if (name == tag.getAttribute("name")
975
and prop._dbus_interface
976
== if_tag.getAttribute("name")):
977
annots.update(getattr
981
for name, value in annots.iteritems():
982
ann_tag = document.createElement(
984
ann_tag.setAttribute("name", name)
985
ann_tag.setAttribute("value", value)
986
tag.appendChild(ann_tag)
987
# Add interface annotation tags
988
for annotation, value in dict(
989
itertools.chain.from_iterable(
990
annotations().iteritems()
991
for name, annotations in
992
self._get_all_dbus_things("interface")
993
if name == if_tag.getAttribute("name")
995
ann_tag = document.createElement("annotation")
996
ann_tag.setAttribute("name", annotation)
997
ann_tag.setAttribute("value", value)
998
if_tag.appendChild(ann_tag)
999
# Add the names to the return values for the
1000
# "org.freedesktop.DBus.Properties" methods
1001
if (if_tag.getAttribute("name")
1002
== "org.freedesktop.DBus.Properties"):
1003
for cn in if_tag.getElementsByTagName("method"):
1004
if cn.getAttribute("name") == "Get":
1005
for arg in cn.getElementsByTagName("arg"):
1006
if (arg.getAttribute("direction")
1008
arg.setAttribute("name", "value")
1009
elif cn.getAttribute("name") == "GetAll":
1010
for arg in cn.getElementsByTagName("arg"):
1011
if (arg.getAttribute("direction")
1013
arg.setAttribute("name", "props")
1014
xmlstring = document.toxml("utf-8")
1016
except (AttributeError, xml.dom.DOMException,
1017
xml.parsers.expat.ExpatError) as error:
1018
logger.error("Failed to override Introspection method",
1023
def datetime_to_dbus (dt, variant_level=0):
1024
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1026
return dbus.String("", variant_level = variant_level)
1027
return dbus.String(dt.isoformat(),
1028
variant_level=variant_level)
1031
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1032
"""A class decorator; applied to a subclass of
1033
dbus.service.Object, it will add alternate D-Bus attributes with
1034
interface names according to the "alt_interface_names" mapping.
1037
@alternate_dbus_names({"org.example.Interface":
1038
"net.example.AlternateInterface"})
1039
class SampleDBusObject(dbus.service.Object):
1040
@dbus.service.method("org.example.Interface")
1041
def SampleDBusMethod():
1044
The above "SampleDBusMethod" on "SampleDBusObject" will be
1045
reachable via two interfaces: "org.example.Interface" and
1046
"net.example.AlternateInterface", the latter of which will have
1047
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1048
"true", unless "deprecate" is passed with a False value.
1050
This works for methods and signals, and also for D-Bus properties
1051
(from DBusObjectWithProperties) and interfaces (from the
1052
dbus_interface_annotations decorator).
1055
for orig_interface_name, alt_interface_name in (
1056
alt_interface_names.iteritems()):
1058
interface_names = set()
1059
# Go though all attributes of the class
1060
for attrname, attribute in inspect.getmembers(cls):
1061
# Ignore non-D-Bus attributes, and D-Bus attributes
1062
# with the wrong interface name
1063
if (not hasattr(attribute, "_dbus_interface")
1064
or not attribute._dbus_interface
1065
.startswith(orig_interface_name)):
1067
# Create an alternate D-Bus interface name based on
1069
alt_interface = (attribute._dbus_interface
1070
.replace(orig_interface_name,
1071
alt_interface_name))
1072
interface_names.add(alt_interface)
1073
# Is this a D-Bus signal?
1074
if getattr(attribute, "_dbus_is_signal", False):
1075
# Extract the original non-method function by
1077
nonmethod_func = (dict(
1078
zip(attribute.func_code.co_freevars,
1079
attribute.__closure__))["func"]
1081
# Create a new, but exactly alike, function
1082
# object, and decorate it to be a new D-Bus signal
1083
# with the alternate D-Bus interface name
1084
new_function = (dbus.service.signal
1086
attribute._dbus_signature)
1087
(types.FunctionType(
1088
nonmethod_func.func_code,
1089
nonmethod_func.func_globals,
1090
nonmethod_func.func_name,
1091
nonmethod_func.func_defaults,
1092
nonmethod_func.func_closure)))
1093
# Copy annotations, if any
1095
new_function._dbus_annotations = (
1096
dict(attribute._dbus_annotations))
1097
except AttributeError:
1099
# Define a creator of a function to call both the
1100
# original and alternate functions, so both the
1101
# original and alternate signals gets sent when
1102
# the function is called
1103
def fixscope(func1, func2):
1104
"""This function is a scope container to pass
1105
func1 and func2 to the "call_both" function
1106
outside of its arguments"""
1107
def call_both(*args, **kwargs):
1108
"""This function will emit two D-Bus
1109
signals by calling func1 and func2"""
1110
func1(*args, **kwargs)
1111
func2(*args, **kwargs)
1113
# Create the "call_both" function and add it to
1115
attr[attrname] = fixscope(attribute, new_function)
1116
# Is this a D-Bus method?
1117
elif getattr(attribute, "_dbus_is_method", False):
1118
# Create a new, but exactly alike, function
1119
# object. Decorate it to be a new D-Bus method
1120
# with the alternate D-Bus interface name. Add it
1122
attr[attrname] = (dbus.service.method
1124
attribute._dbus_in_signature,
1125
attribute._dbus_out_signature)
1127
(attribute.func_code,
1128
attribute.func_globals,
1129
attribute.func_name,
1130
attribute.func_defaults,
1131
attribute.func_closure)))
1132
# Copy annotations, if any
1134
attr[attrname]._dbus_annotations = (
1135
dict(attribute._dbus_annotations))
1136
except AttributeError:
1138
# Is this a D-Bus property?
1139
elif getattr(attribute, "_dbus_is_property", False):
1140
# Create a new, but exactly alike, function
1141
# object, and decorate it to be a new D-Bus
1142
# property with the alternate D-Bus interface
1143
# name. Add it to the class.
1144
attr[attrname] = (dbus_service_property
1146
attribute._dbus_signature,
1147
attribute._dbus_access,
1149
._dbus_get_args_options
1152
(attribute.func_code,
1153
attribute.func_globals,
1154
attribute.func_name,
1155
attribute.func_defaults,
1156
attribute.func_closure)))
1157
# Copy annotations, if any
1159
attr[attrname]._dbus_annotations = (
1160
dict(attribute._dbus_annotations))
1161
except AttributeError:
1163
# Is this a D-Bus interface?
1164
elif getattr(attribute, "_dbus_is_interface", False):
1165
# Create a new, but exactly alike, function
1166
# object. Decorate it to be a new D-Bus interface
1167
# with the alternate D-Bus interface name. Add it
1169
attr[attrname] = (dbus_interface_annotations
1172
(attribute.func_code,
1173
attribute.func_globals,
1174
attribute.func_name,
1175
attribute.func_defaults,
1176
attribute.func_closure)))
1178
# Deprecate all alternate interfaces
1179
iname="_AlternateDBusNames_interface_annotation{0}"
1180
for interface_name in interface_names:
1181
@dbus_interface_annotations(interface_name)
1183
return { "org.freedesktop.DBus.Deprecated":
1185
# Find an unused name
1186
for aname in (iname.format(i)
1187
for i in itertools.count()):
1188
if aname not in attr:
1192
# Replace the class with a new subclass of it with
1193
# methods, signals, etc. as created above.
1194
cls = type(b"{0}Alternate".format(cls.__name__),
1200
@alternate_dbus_interfaces({"se.recompile.Mandos":
1201
"se.bsnet.fukt.Mandos"})
1202
class ClientDBus(Client, DBusObjectWithProperties):
1203
"""A Client class using D-Bus
1206
dbus_object_path: dbus.ObjectPath
1207
bus: dbus.SystemBus()
1210
runtime_expansions = (Client.runtime_expansions
1211
+ ("dbus_object_path",))
1213
# dbus.service.Object doesn't use super(), so we can't either.
1215
def __init__(self, bus = None, *args, **kwargs):
1217
Client.__init__(self, *args, **kwargs)
1218
# Only now, when this client is initialized, can it show up on
1220
client_object_name = unicode(self.name).translate(
1221
{ord("."): ord("_"),
1222
ord("-"): ord("_")})
1223
self.dbus_object_path = (dbus.ObjectPath
1224
("/clients/" + client_object_name))
1225
DBusObjectWithProperties.__init__(self, self.bus,
1226
self.dbus_object_path)
1228
def notifychangeproperty(transform_func,
1229
dbus_name, type_func=lambda x: x,
1231
""" Modify a variable so that it's a property which announces
1232
its changes to DBus.
1234
transform_fun: Function that takes a value and a variant_level
1235
and transforms it to a D-Bus type.
1236
dbus_name: D-Bus name of the variable
1237
type_func: Function that transform the value before sending it
1238
to the D-Bus. Default: no transform
1239
variant_level: D-Bus variant level. Default: 1
1241
attrname = "_{0}".format(dbus_name)
1242
def setter(self, value):
1243
if hasattr(self, "dbus_object_path"):
1244
if (not hasattr(self, attrname) or
1245
type_func(getattr(self, attrname, None))
1246
!= type_func(value)):
1247
dbus_value = transform_func(type_func(value),
1250
self.PropertyChanged(dbus.String(dbus_name),
1252
setattr(self, attrname, value)
1254
return property(lambda self: getattr(self, attrname), setter)
1256
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1257
approvals_pending = notifychangeproperty(dbus.Boolean,
1260
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1261
last_enabled = notifychangeproperty(datetime_to_dbus,
1263
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1264
type_func = lambda checker:
1265
checker is not None)
1266
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1268
last_checker_status = notifychangeproperty(dbus.Int16,
1269
"LastCheckerStatus")
1270
last_approval_request = notifychangeproperty(
1271
datetime_to_dbus, "LastApprovalRequest")
1272
approved_by_default = notifychangeproperty(dbus.Boolean,
1273
"ApprovedByDefault")
1274
approval_delay = notifychangeproperty(dbus.UInt64,
1277
timedelta_to_milliseconds)
1278
approval_duration = notifychangeproperty(
1279
dbus.UInt64, "ApprovalDuration",
1280
type_func = timedelta_to_milliseconds)
1281
host = notifychangeproperty(dbus.String, "Host")
1282
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1284
timedelta_to_milliseconds)
1285
extended_timeout = notifychangeproperty(
1286
dbus.UInt64, "ExtendedTimeout",
1287
type_func = timedelta_to_milliseconds)
1288
interval = notifychangeproperty(dbus.UInt64,
1291
timedelta_to_milliseconds)
1292
checker_command = notifychangeproperty(dbus.String, "Checker")
1294
del notifychangeproperty
1296
def __del__(self, *args, **kwargs):
1298
self.remove_from_connection()
1301
if hasattr(DBusObjectWithProperties, "__del__"):
1302
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1303
Client.__del__(self, *args, **kwargs)
1305
def checker_callback(self, pid, condition, command,
1307
self.checker_callback_tag = None
1309
if os.WIFEXITED(condition):
1310
exitstatus = os.WEXITSTATUS(condition)
1312
self.CheckerCompleted(dbus.Int16(exitstatus),
1313
dbus.Int64(condition),
1314
dbus.String(command))
1317
self.CheckerCompleted(dbus.Int16(-1),
1318
dbus.Int64(condition),
1319
dbus.String(command))
1321
return Client.checker_callback(self, pid, condition, command,
1324
def start_checker(self, *args, **kwargs):
1325
old_checker = self.checker
1326
if self.checker is not None:
1327
old_checker_pid = self.checker.pid
1329
old_checker_pid = None
1330
r = Client.start_checker(self, *args, **kwargs)
1331
# Only if new checker process was started
1332
if (self.checker is not None
1333
and old_checker_pid != self.checker.pid):
1335
self.CheckerStarted(self.current_checker_command)
1338
def _reset_approved(self):
1339
self.approved = None
1342
def approve(self, value=True):
1343
self.approved = value
1344
gobject.timeout_add(timedelta_to_milliseconds
1345
(self.approval_duration),
1346
self._reset_approved)
1347
self.send_changedstate()
1349
## D-Bus methods, signals & properties
1350
_interface = "se.recompile.Mandos.Client"
1354
@dbus_interface_annotations(_interface)
1356
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1361
# CheckerCompleted - signal
1362
@dbus.service.signal(_interface, signature="nxs")
1363
def CheckerCompleted(self, exitcode, waitstatus, command):
1367
# CheckerStarted - signal
1368
@dbus.service.signal(_interface, signature="s")
1369
def CheckerStarted(self, command):
1373
# PropertyChanged - signal
1374
@dbus.service.signal(_interface, signature="sv")
1375
def PropertyChanged(self, property, value):
1379
# GotSecret - signal
1380
@dbus.service.signal(_interface)
1381
def GotSecret(self):
1383
Is sent after a successful transfer of secret from the Mandos
1384
server to mandos-client
1389
@dbus.service.signal(_interface, signature="s")
1390
def Rejected(self, reason):
1394
# NeedApproval - signal
1395
@dbus.service.signal(_interface, signature="tb")
1396
def NeedApproval(self, timeout, default):
1398
return self.need_approval()
1403
@dbus.service.method(_interface, in_signature="b")
1404
def Approve(self, value):
1407
# CheckedOK - method
1408
@dbus.service.method(_interface)
1409
def CheckedOK(self):
1413
@dbus.service.method(_interface)
1418
# StartChecker - method
1419
@dbus.service.method(_interface)
1420
def StartChecker(self):
1422
self.start_checker()
1425
@dbus.service.method(_interface)
1430
# StopChecker - method
1431
@dbus.service.method(_interface)
1432
def StopChecker(self):
1437
# ApprovalPending - property
1438
@dbus_service_property(_interface, signature="b", access="read")
1439
def ApprovalPending_dbus_property(self):
1440
return dbus.Boolean(bool(self.approvals_pending))
1442
# ApprovedByDefault - property
1443
@dbus_service_property(_interface, signature="b",
1445
def ApprovedByDefault_dbus_property(self, value=None):
1446
if value is None: # get
1447
return dbus.Boolean(self.approved_by_default)
1448
self.approved_by_default = bool(value)
1450
# ApprovalDelay - property
1451
@dbus_service_property(_interface, signature="t",
1453
def ApprovalDelay_dbus_property(self, value=None):
1454
if value is None: # get
1455
return dbus.UInt64(self.approval_delay_milliseconds())
1456
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1458
# ApprovalDuration - property
1459
@dbus_service_property(_interface, signature="t",
1461
def ApprovalDuration_dbus_property(self, value=None):
1462
if value is None: # get
1463
return dbus.UInt64(timedelta_to_milliseconds(
1464
self.approval_duration))
1465
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1468
@dbus_service_property(_interface, signature="s", access="read")
1469
def Name_dbus_property(self):
1470
return dbus.String(self.name)
1472
# Fingerprint - property
1473
@dbus_service_property(_interface, signature="s", access="read")
1474
def Fingerprint_dbus_property(self):
1475
return dbus.String(self.fingerprint)
1478
@dbus_service_property(_interface, signature="s",
1480
def Host_dbus_property(self, value=None):
1481
if value is None: # get
1482
return dbus.String(self.host)
1483
self.host = unicode(value)
1485
# Created - property
1486
@dbus_service_property(_interface, signature="s", access="read")
1487
def Created_dbus_property(self):
1488
return datetime_to_dbus(self.created)
1490
# LastEnabled - property
1491
@dbus_service_property(_interface, signature="s", access="read")
1492
def LastEnabled_dbus_property(self):
1493
return datetime_to_dbus(self.last_enabled)
1495
# Enabled - property
1496
@dbus_service_property(_interface, signature="b",
1498
def Enabled_dbus_property(self, value=None):
1499
if value is None: # get
1500
return dbus.Boolean(self.enabled)
1506
# LastCheckedOK - property
1507
@dbus_service_property(_interface, signature="s",
1509
def LastCheckedOK_dbus_property(self, value=None):
1510
if value is not None:
1513
return datetime_to_dbus(self.last_checked_ok)
1515
# LastCheckerStatus - property
1516
@dbus_service_property(_interface, signature="n",
1518
def LastCheckerStatus_dbus_property(self):
1519
return dbus.Int16(self.last_checker_status)
1521
# Expires - property
1522
@dbus_service_property(_interface, signature="s", access="read")
1523
def Expires_dbus_property(self):
1524
return datetime_to_dbus(self.expires)
1526
# LastApprovalRequest - property
1527
@dbus_service_property(_interface, signature="s", access="read")
1528
def LastApprovalRequest_dbus_property(self):
1529
return datetime_to_dbus(self.last_approval_request)
1531
# Timeout - property
1532
@dbus_service_property(_interface, signature="t",
1534
def Timeout_dbus_property(self, value=None):
1535
if value is None: # get
1536
return dbus.UInt64(self.timeout_milliseconds())
1537
old_timeout = self.timeout
1538
self.timeout = datetime.timedelta(0, 0, 0, value)
1539
# Reschedule disabling
1541
now = datetime.datetime.utcnow()
1542
self.expires += self.timeout - old_timeout
1543
if self.expires <= now:
1544
# The timeout has passed
1547
if (getattr(self, "disable_initiator_tag", None)
1550
gobject.source_remove(self.disable_initiator_tag)
1551
self.disable_initiator_tag = (
1552
gobject.timeout_add(
1553
timedelta_to_milliseconds(self.expires - now),
1556
# ExtendedTimeout - property
1557
@dbus_service_property(_interface, signature="t",
1559
def ExtendedTimeout_dbus_property(self, value=None):
1560
if value is None: # get
1561
return dbus.UInt64(self.extended_timeout_milliseconds())
1562
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1564
# Interval - property
1565
@dbus_service_property(_interface, signature="t",
1567
def Interval_dbus_property(self, value=None):
1568
if value is None: # get
1569
return dbus.UInt64(self.interval_milliseconds())
1570
self.interval = datetime.timedelta(0, 0, 0, value)
1571
if getattr(self, "checker_initiator_tag", None) is None:
1574
# Reschedule checker run
1575
gobject.source_remove(self.checker_initiator_tag)
1576
self.checker_initiator_tag = (gobject.timeout_add
1577
(value, self.start_checker))
1578
self.start_checker() # Start one now, too
1580
# Checker - property
1581
@dbus_service_property(_interface, signature="s",
1583
def Checker_dbus_property(self, value=None):
1584
if value is None: # get
1585
return dbus.String(self.checker_command)
1586
self.checker_command = unicode(value)
1588
# CheckerRunning - property
1589
@dbus_service_property(_interface, signature="b",
1591
def CheckerRunning_dbus_property(self, value=None):
1592
if value is None: # get
1593
return dbus.Boolean(self.checker is not None)
1595
self.start_checker()
1599
# ObjectPath - property
1600
@dbus_service_property(_interface, signature="o", access="read")
1601
def ObjectPath_dbus_property(self):
1602
return self.dbus_object_path # is already a dbus.ObjectPath
1605
@dbus_service_property(_interface, signature="ay",
1606
access="write", byte_arrays=True)
1607
def Secret_dbus_property(self, value):
1608
self.secret = str(value)
1613
class ProxyClient(object):
1614
def __init__(self, child_pipe, fpr, address):
1615
self._pipe = child_pipe
1616
self._pipe.send(('init', fpr, address))
1617
if not self._pipe.recv():
1620
def __getattribute__(self, name):
1622
return super(ProxyClient, self).__getattribute__(name)
1623
self._pipe.send(('getattr', name))
1624
data = self._pipe.recv()
1625
if data[0] == 'data':
1627
if data[0] == 'function':
1628
def func(*args, **kwargs):
1629
self._pipe.send(('funcall', name, args, kwargs))
1630
return self._pipe.recv()[1]
1633
def __setattr__(self, name, value):
1635
return super(ProxyClient, self).__setattr__(name, value)
1636
self._pipe.send(('setattr', name, value))
1639
class ClientHandler(socketserver.BaseRequestHandler, object):
1640
"""A class to handle client connections.
1642
Instantiated once for each connection to handle it.
380
def still_valid(self):
381
"""Has the timeout not yet passed for this client?"""
382
now = datetime.datetime.now()
383
if self.last_checked_ok is None:
384
return now < (self.created + self.timeout)
386
return now < (self.last_checked_ok + self.timeout)
389
def peer_certificate(session):
390
"Return the peer's OpenPGP certificate as a bytestring"
391
# If not an OpenPGP certificate...
392
if gnutls.library.functions.gnutls_certificate_type_get\
393
(session._c_object) \
394
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
395
# ...do the normal thing
396
return session.peer_certificate
397
list_size = ctypes.c_uint()
398
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
399
(session._c_object, ctypes.byref(list_size))
400
if list_size.value == 0:
403
return ctypes.string_at(cert.data, cert.size)
406
def fingerprint(openpgp):
407
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
408
# New GnuTLS "datum" with the OpenPGP public key
409
datum = gnutls.library.types.gnutls_datum_t\
410
(ctypes.cast(ctypes.c_char_p(openpgp),
411
ctypes.POINTER(ctypes.c_ubyte)),
412
ctypes.c_uint(len(openpgp)))
413
# New empty GnuTLS certificate
414
crt = gnutls.library.types.gnutls_openpgp_crt_t()
415
gnutls.library.functions.gnutls_openpgp_crt_init\
417
# Import the OpenPGP public key into the certificate
418
gnutls.library.functions.gnutls_openpgp_crt_import\
419
(crt, ctypes.byref(datum),
420
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
421
# Verify the self signature in the key
422
crtverify = ctypes.c_uint()
423
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
424
(crt, 0, ctypes.byref(crtverify))
425
if crtverify.value != 0:
426
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
427
raise gnutls.errors.CertificateSecurityError("Verify failed")
428
# New buffer for the fingerprint
429
buf = ctypes.create_string_buffer(20)
430
buf_len = ctypes.c_size_t()
431
# Get the fingerprint from the certificate into the buffer
432
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
433
(crt, ctypes.byref(buf), ctypes.byref(buf_len))
434
# Deinit the certificate
435
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
436
# Convert the buffer to a Python bytestring
437
fpr = ctypes.string_at(buf, buf_len.value)
438
# Convert the bytestring to hexadecimal notation
439
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
443
class TCP_handler(SocketServer.BaseRequestHandler, object):
444
"""A TCP request handler class.
445
Instantiated by IPv6_TCPServer for each request to handle it.
1643
446
Note: This will run in its own forked process."""
1645
448
def handle(self):
1646
with contextlib.closing(self.server.child_pipe) as child_pipe:
1647
logger.info("TCP connection from: %s",
1648
unicode(self.client_address))
1649
logger.debug("Pipe FD: %d",
1650
self.server.child_pipe.fileno())
1652
session = (gnutls.connection
1653
.ClientSession(self.request,
1655
.X509Credentials()))
1657
# Note: gnutls.connection.X509Credentials is really a
1658
# generic GnuTLS certificate credentials object so long as
1659
# no X.509 keys are added to it. Therefore, we can use it
1660
# here despite using OpenPGP certificates.
1662
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1663
# "+AES-256-CBC", "+SHA1",
1664
# "+COMP-NULL", "+CTYPE-OPENPGP",
1666
# Use a fallback default, since this MUST be set.
1667
priority = self.server.gnutls_priority
1668
if priority is None:
1670
(gnutls.library.functions
1671
.gnutls_priority_set_direct(session._c_object,
1674
# Start communication using the Mandos protocol
1675
# Get protocol number
1676
line = self.request.makefile().readline()
1677
logger.debug("Protocol version: %r", line)
1679
if int(line.strip().split()[0]) > 1:
1681
except (ValueError, IndexError, RuntimeError) as error:
1682
logger.error("Unknown protocol version: %s", error)
1685
# Start GnuTLS connection
1688
except gnutls.errors.GNUTLSError as error:
1689
logger.warning("Handshake failed: %s", error)
1690
# Do not run session.bye() here: the session is not
1691
# established. Just abandon the request.
1693
logger.debug("Handshake succeeded")
1695
approval_required = False
1698
fpr = self.fingerprint(self.peer_certificate
1701
gnutls.errors.GNUTLSError) as error:
1702
logger.warning("Bad certificate: %s", error)
1704
logger.debug("Fingerprint: %s", fpr)
1707
client = ProxyClient(child_pipe, fpr,
1708
self.client_address)
1712
if client.approval_delay:
1713
delay = client.approval_delay
1714
client.approvals_pending += 1
1715
approval_required = True
1718
if not client.enabled:
1719
logger.info("Client %s is disabled",
1721
if self.server.use_dbus:
1723
client.Rejected("Disabled")
1726
if client.approved or not client.approval_delay:
1727
#We are approved or approval is disabled
1729
elif client.approved is None:
1730
logger.info("Client %s needs approval",
1732
if self.server.use_dbus:
1734
client.NeedApproval(
1735
client.approval_delay_milliseconds(),
1736
client.approved_by_default)
1738
logger.warning("Client %s was not approved",
1740
if self.server.use_dbus:
1742
client.Rejected("Denied")
1745
#wait until timeout or approved
1746
time = datetime.datetime.now()
1747
client.changedstate.acquire()
1748
client.changedstate.wait(
1749
float(timedelta_to_milliseconds(delay)
1751
client.changedstate.release()
1752
time2 = datetime.datetime.now()
1753
if (time2 - time) >= delay:
1754
if not client.approved_by_default:
1755
logger.warning("Client %s timed out while"
1756
" waiting for approval",
1758
if self.server.use_dbus:
1760
client.Rejected("Approval timed out")
1765
delay -= time2 - time
1768
while sent_size < len(client.secret):
1770
sent = session.send(client.secret[sent_size:])
1771
except gnutls.errors.GNUTLSError as error:
1772
logger.warning("gnutls send failed",
1775
logger.debug("Sent: %d, remaining: %d",
1776
sent, len(client.secret)
1777
- (sent_size + sent))
1780
logger.info("Sending secret to %s", client.name)
1781
# bump the timeout using extended_timeout
1782
client.bump_timeout(client.extended_timeout)
1783
if self.server.use_dbus:
1788
if approval_required:
1789
client.approvals_pending -= 1
1792
except gnutls.errors.GNUTLSError as error:
1793
logger.warning("GnuTLS bye failed",
1797
def peer_certificate(session):
1798
"Return the peer's OpenPGP certificate as a bytestring"
1799
# If not an OpenPGP certificate...
1800
if (gnutls.library.functions
1801
.gnutls_certificate_type_get(session._c_object)
1802
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1803
# ...do the normal thing
1804
return session.peer_certificate
1805
list_size = ctypes.c_uint(1)
1806
cert_list = (gnutls.library.functions
1807
.gnutls_certificate_get_peers
1808
(session._c_object, ctypes.byref(list_size)))
1809
if not bool(cert_list) and list_size.value != 0:
1810
raise gnutls.errors.GNUTLSError("error getting peer"
1812
if list_size.value == 0:
1815
return ctypes.string_at(cert.data, cert.size)
1818
def fingerprint(openpgp):
1819
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1820
# New GnuTLS "datum" with the OpenPGP public key
1821
datum = (gnutls.library.types
1822
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1825
ctypes.c_uint(len(openpgp))))
1826
# New empty GnuTLS certificate
1827
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1828
(gnutls.library.functions
1829
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1830
# Import the OpenPGP public key into the certificate
1831
(gnutls.library.functions
1832
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1833
gnutls.library.constants
1834
.GNUTLS_OPENPGP_FMT_RAW))
1835
# Verify the self signature in the key
1836
crtverify = ctypes.c_uint()
1837
(gnutls.library.functions
1838
.gnutls_openpgp_crt_verify_self(crt, 0,
1839
ctypes.byref(crtverify)))
1840
if crtverify.value != 0:
1841
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1842
raise (gnutls.errors.CertificateSecurityError
1844
# New buffer for the fingerprint
1845
buf = ctypes.create_string_buffer(20)
1846
buf_len = ctypes.c_size_t()
1847
# Get the fingerprint from the certificate into the buffer
1848
(gnutls.library.functions
1849
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1850
ctypes.byref(buf_len)))
1851
# Deinit the certificate
1852
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1853
# Convert the buffer to a Python bytestring
1854
fpr = ctypes.string_at(buf, buf_len.value)
1855
# Convert the bytestring to hexadecimal notation
1856
hex_fpr = binascii.hexlify(fpr).upper()
1860
class MultiprocessingMixIn(object):
1861
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1862
def sub_process_main(self, request, address):
1864
self.finish_request(request, address)
1866
self.handle_error(request, address)
1867
self.close_request(request)
1869
def process_request(self, request, address):
1870
"""Start a new process to process the request."""
1871
proc = multiprocessing.Process(target = self.sub_process_main,
1872
args = (request, address))
1877
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1878
""" adds a pipe to the MixIn """
1879
def process_request(self, request, client_address):
1880
"""Overrides and wraps the original process_request().
1882
This function creates a new pipe in self.pipe
1884
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1886
proc = MultiprocessingMixIn.process_request(self, request,
1888
self.child_pipe.close()
1889
self.add_pipe(parent_pipe, proc)
1891
def add_pipe(self, parent_pipe, proc):
1892
"""Dummy function; override as necessary"""
1893
raise NotImplementedError
1896
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1897
socketserver.TCPServer, object):
1898
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
449
logger.info(u"TCP connection from: %s",
450
unicode(self.client_address))
451
session = gnutls.connection.ClientSession\
452
(self.request, gnutls.connection.X509Credentials())
454
line = self.request.makefile().readline()
455
logger.debug(u"Protocol version: %r", line)
457
if int(line.strip().split()[0]) > 1:
459
except (ValueError, IndexError, RuntimeError), error:
460
logger.error(u"Unknown protocol version: %s", error)
463
# Note: gnutls.connection.X509Credentials is really a generic
464
# GnuTLS certificate credentials object so long as no X.509
465
# keys are added to it. Therefore, we can use it here despite
466
# using OpenPGP certificates.
468
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
469
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
471
priority = "NORMAL" # Fallback default, since this
473
if self.server.settings["priority"]:
474
priority = self.server.settings["priority"]
475
gnutls.library.functions.gnutls_priority_set_direct\
476
(session._c_object, priority, None)
480
except gnutls.errors.GNUTLSError, error:
481
logger.warning(u"Handshake failed: %s", error)
482
# Do not run session.bye() here: the session is not
483
# established. Just abandon the request.
486
fpr = fingerprint(peer_certificate(session))
487
except (TypeError, gnutls.errors.GNUTLSError), error:
488
logger.warning(u"Bad certificate: %s", error)
491
logger.debug(u"Fingerprint: %s", fpr)
493
for c in self.server.clients:
494
if c.fingerprint == fpr:
498
logger.warning(u"Client not found for fingerprint: %s",
502
# Have to check if client.still_valid(), since it is possible
503
# that the client timed out while establishing the GnuTLS
505
if not client.still_valid():
506
logger.warning(u"Client %(name)s is invalid",
511
while sent_size < len(client.secret):
512
sent = session.send(client.secret[sent_size:])
513
logger.debug(u"Sent: %d, remaining: %d",
514
sent, len(client.secret)
515
- (sent_size + sent))
520
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
521
"""IPv6 TCP server. Accepts 'None' as address and/or port.
523
settings: Server settings
524
clients: Set() of Client objects
1901
525
enabled: Boolean; whether this server is activated yet
1902
interface: None or a network interface name (string)
1903
use_ipv6: Boolean; to use IPv6 or not
1905
def __init__(self, server_address, RequestHandlerClass,
1906
interface=None, use_ipv6=True):
1907
self.interface = interface
1909
self.address_family = socket.AF_INET6
1910
socketserver.TCPServer.__init__(self, server_address,
1911
RequestHandlerClass)
527
address_family = socket.AF_INET6
528
def __init__(self, *args, **kwargs):
529
if "settings" in kwargs:
530
self.settings = kwargs["settings"]
531
del kwargs["settings"]
532
if "clients" in kwargs:
533
self.clients = kwargs["clients"]
534
del kwargs["clients"]
536
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1912
537
def server_bind(self):
1913
538
"""This overrides the normal server_bind() function
1914
539
to bind to an interface if one was specified, and also NOT to
1915
540
bind to an address or port if they were not specified."""
1916
if self.interface is not None:
1917
if SO_BINDTODEVICE is None:
1918
logger.error("SO_BINDTODEVICE does not exist;"
1919
" cannot bind to interface %s",
1923
self.socket.setsockopt(socket.SOL_SOCKET,
1927
except socket.error as error:
1928
if error[0] == errno.EPERM:
1929
logger.error("No permission to"
1930
" bind to interface %s",
1932
elif error[0] == errno.ENOPROTOOPT:
1933
logger.error("SO_BINDTODEVICE not available;"
1934
" cannot bind to interface %s",
541
if self.settings["interface"]:
542
# 25 is from /usr/include/asm-i486/socket.h
543
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
545
self.socket.setsockopt(socket.SOL_SOCKET,
547
self.settings["interface"])
548
except socket.error, error:
549
if error[0] == errno.EPERM:
550
logger.error(u"No permission to"
551
u" bind to interface %s",
552
self.settings["interface"])
1938
555
# Only bind(2) the socket if we really need to.
1939
556
if self.server_address[0] or self.server_address[1]:
1940
557
if not self.server_address[0]:
1941
if self.address_family == socket.AF_INET6:
1942
any_address = "::" # in6addr_any
1944
any_address = socket.INADDR_ANY
1945
self.server_address = (any_address,
559
self.server_address = (in6addr_any,
1946
560
self.server_address[1])
1947
561
elif not self.server_address[1]:
1948
562
self.server_address = (self.server_address[0],
1950
# if self.interface:
564
# if self.settings["interface"]:
1951
565
# self.server_address = (self.server_address[0],
1954
568
# if_nametoindex
1956
return socketserver.TCPServer.server_bind(self)
1959
class MandosServer(IPv6_TCPServer):
1963
clients: set of Client objects
1964
gnutls_priority GnuTLS priority string
1965
use_dbus: Boolean; to emit D-Bus signals or not
1967
Assumes a gobject.MainLoop event loop.
1969
def __init__(self, server_address, RequestHandlerClass,
1970
interface=None, use_ipv6=True, clients=None,
1971
gnutls_priority=None, use_dbus=True):
1972
self.enabled = False
1973
self.clients = clients
1974
if self.clients is None:
1976
self.use_dbus = use_dbus
1977
self.gnutls_priority = gnutls_priority
1978
IPv6_TCPServer.__init__(self, server_address,
1979
RequestHandlerClass,
1980
interface = interface,
1981
use_ipv6 = use_ipv6)
571
return super(IPv6_TCPServer, self).server_bind()
1982
572
def server_activate(self):
1983
573
if self.enabled:
1984
return socketserver.TCPServer.server_activate(self)
574
return super(IPv6_TCPServer, self).server_activate()
1986
575
def enable(self):
1987
576
self.enabled = True
1989
def add_pipe(self, parent_pipe, proc):
1990
# Call "handle_ipc" for both data and EOF events
1991
gobject.io_add_watch(parent_pipe.fileno(),
1992
gobject.IO_IN | gobject.IO_HUP,
1993
functools.partial(self.handle_ipc,
1998
def handle_ipc(self, source, condition, parent_pipe=None,
1999
proc = None, client_object=None):
2000
# error, or the other end of multiprocessing.Pipe has closed
2001
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2002
# Wait for other process to exit
2006
# Read a request from the child
2007
request = parent_pipe.recv()
2008
command = request[0]
2010
if command == 'init':
2012
address = request[2]
2014
for c in self.clients.itervalues():
2015
if c.fingerprint == fpr:
2019
logger.info("Client not found for fingerprint: %s, ad"
2020
"dress: %s", fpr, address)
2023
mandos_dbus_service.ClientNotFound(fpr,
2025
parent_pipe.send(False)
2028
gobject.io_add_watch(parent_pipe.fileno(),
2029
gobject.IO_IN | gobject.IO_HUP,
2030
functools.partial(self.handle_ipc,
2036
parent_pipe.send(True)
2037
# remove the old hook in favor of the new above hook on
2040
if command == 'funcall':
2041
funcname = request[1]
2045
parent_pipe.send(('data', getattr(client_object,
2049
if command == 'getattr':
2050
attrname = request[1]
2051
if callable(client_object.__getattribute__(attrname)):
2052
parent_pipe.send(('function',))
2054
parent_pipe.send(('data', client_object
2055
.__getattribute__(attrname)))
2057
if command == 'setattr':
2058
attrname = request[1]
2060
setattr(client_object, attrname, value)
2065
579
def string_to_delta(interval):
2066
580
"""Parse a string and return a datetime.timedelta
2068
582
>>> string_to_delta('7d')
2069
583
datetime.timedelta(7)
2070
584
>>> string_to_delta('60s')
2184
721
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2185
722
"servicename": "Mandos",
2190
"statedir": "/var/lib/mandos"
2193
725
# Parse config file for server-global settings
2194
server_config = configparser.SafeConfigParser(server_defaults)
726
server_config = ConfigParser.SafeConfigParser(server_defaults)
2195
727
del server_defaults
2196
server_config.read(os.path.join(options.configdir,
728
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2198
729
# Convert the SafeConfigParser object to a dict
2199
730
server_settings = server_config.defaults()
2200
# Use the appropriate methods on the non-string config options
2201
for option in ("debug", "use_dbus", "use_ipv6"):
2202
server_settings[option] = server_config.getboolean("DEFAULT",
2204
if server_settings["port"]:
2205
server_settings["port"] = server_config.getint("DEFAULT",
731
# Use getboolean on the boolean config option
732
server_settings["debug"] = server_config.getboolean\
2207
734
del server_config
2209
736
# Override the settings from the config file with command line
2210
737
# options, if set.
2211
738
for option in ("interface", "address", "port", "debug",
2212
"priority", "servicename", "configdir",
2213
"use_dbus", "use_ipv6", "debuglevel", "restore",
739
"priority", "servicename", "configdir"):
2215
740
value = getattr(options, option)
2216
741
if value is not None:
2217
742
server_settings[option] = value
2219
# Force all strings to be unicode
2220
for option in server_settings.keys():
2221
if type(server_settings[option]) is str:
2222
server_settings[option] = unicode(server_settings[option])
2223
744
# Now we have our good server settings in "server_settings"
2225
##################################################################
2228
746
debug = server_settings["debug"]
2229
debuglevel = server_settings["debuglevel"]
2230
use_dbus = server_settings["use_dbus"]
2231
use_ipv6 = server_settings["use_ipv6"]
2232
stored_state_path = os.path.join(server_settings["statedir"],
2236
initlogger(debug, logging.DEBUG)
2241
level = getattr(logging, debuglevel.upper())
2242
initlogger(debug, level)
749
syslogger.setLevel(logging.WARNING)
750
console.setLevel(logging.WARNING)
2244
752
if server_settings["servicename"] != "Mandos":
2245
syslogger.setFormatter(logging.Formatter
2246
('Mandos ({0}) [%(process)d]:'
2247
' %(levelname)s: %(message)s'
2248
.format(server_settings
753
syslogger.setFormatter(logging.Formatter\
754
('Mandos (%s): %%(levelname)s:'
756
% server_settings["servicename"]))
2251
758
# Parse config file with clients
2252
client_config = configparser.SafeConfigParser(Client
759
client_defaults = { "timeout": "1h",
761
"checker": "fping -q -- %(host)s",
764
client_config = ConfigParser.SafeConfigParser(client_defaults)
2254
765
client_config.read(os.path.join(server_settings["configdir"],
2255
766
"clients.conf"))
2257
global mandos_dbus_service
2258
mandos_dbus_service = None
2260
tcp_server = MandosServer((server_settings["address"],
2261
server_settings["port"]),
2263
interface=(server_settings["interface"]
2267
server_settings["priority"],
2270
pidfilename = "/var/run/mandos.pid"
2272
pidfile = open(pidfilename, "w")
2273
except IOError as e:
2274
logger.error("Could not open file %r", pidfilename,
2277
for name in ("_mandos", "mandos", "nobody"):
2279
uid = pwd.getpwnam(name).pw_uid
2280
gid = pwd.getpwnam(name).pw_gid
769
tcp_server = IPv6_TCPServer((server_settings["address"],
770
server_settings["port"]),
772
settings=server_settings,
774
pidfilename = "/var/run/mandos.pid"
776
pidfile = open(pidfilename, "w")
777
except IOError, error:
778
logger.error("Could not open file %r", pidfilename)
783
uid = pwd.getpwnam("mandos").pw_uid
786
uid = pwd.getpwnam("nobody").pw_uid
790
gid = pwd.getpwnam("mandos").pw_gid
793
gid = pwd.getpwnam("nogroup").pw_gid
2290
except OSError as error:
799
except OSError, error:
2291
800
if error[0] != errno.EPERM:
2295
# Enable all possible GnuTLS debugging
2297
# "Use a log level over 10 to enable all debugging options."
2299
gnutls.library.functions.gnutls_global_set_log_level(11)
2301
@gnutls.library.types.gnutls_log_func
2302
def debug_gnutls(level, string):
2303
logger.debug("GnuTLS: %s", string[:-1])
2305
(gnutls.library.functions
2306
.gnutls_global_set_log_function(debug_gnutls))
2308
# Redirect stdin so all checkers get /dev/null
2309
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2310
os.dup2(null, sys.stdin.fileno())
2314
# Need to fork before connecting to D-Bus
2316
# Close all input and output, do double fork, etc.
2319
gobject.threads_init()
804
service = AvahiService(name = server_settings["servicename"],
805
servicetype = "_mandos._tcp", )
806
if server_settings["interface"]:
807
service.interface = if_nametoindex\
808
(server_settings["interface"])
2321
810
global main_loop
2322
813
# From the Avahi example code
2323
DBusGMainLoop(set_as_default=True)
814
DBusGMainLoop(set_as_default=True )
2324
815
main_loop = gobject.MainLoop()
2325
816
bus = dbus.SystemBus()
817
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
818
avahi.DBUS_PATH_SERVER),
819
avahi.DBUS_INTERFACE_SERVER)
2326
820
# End of Avahi example code
2329
bus_name = dbus.service.BusName("se.recompile.Mandos",
2330
bus, do_not_queue=True)
2331
old_bus_name = (dbus.service.BusName
2332
("se.bsnet.fukt.Mandos", bus,
2334
except dbus.exceptions.NameExistsException as e:
2335
logger.error("Disabling D-Bus:", exc_info=e)
2337
server_settings["use_dbus"] = False
2338
tcp_server.use_dbus = False
2339
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2340
service = AvahiServiceToSyslog(name =
2341
server_settings["servicename"],
2342
servicetype = "_mandos._tcp",
2343
protocol = protocol, bus = bus)
2344
if server_settings["interface"]:
2345
service.interface = (if_nametoindex
2346
(str(server_settings["interface"])))
2348
global multiprocessing_manager
2349
multiprocessing_manager = multiprocessing.Manager()
2351
client_class = Client
2353
client_class = functools.partial(ClientDBus, bus = bus)
2355
client_settings = Client.config_parser(client_config)
2356
old_client_settings = {}
2359
# Get client data and settings from last running state.
2360
if server_settings["restore"]:
2362
with open(stored_state_path, "rb") as stored_state:
2363
clients_data, old_client_settings = (pickle.load
2365
os.remove(stored_state_path)
2366
except IOError as e:
2367
if e.errno == errno.ENOENT:
2368
logger.warning("Could not load persistent state: {0}"
2369
.format(os.strerror(e.errno)))
2371
logger.critical("Could not load persistent state:",
2374
except EOFError as e:
2375
logger.warning("Could not load persistent state: "
2376
"EOFError:", exc_info=e)
2378
with PGPEngine() as pgp:
2379
for client_name, client in clients_data.iteritems():
2380
# Decide which value to use after restoring saved state.
2381
# We have three different values: Old config file,
2382
# new config file, and saved state.
2383
# New config value takes precedence if it differs from old
2384
# config value, otherwise use saved state.
2385
for name, value in client_settings[client_name].items():
2387
# For each value in new config, check if it
2388
# differs from the old config value (Except for
2389
# the "secret" attribute)
2390
if (name != "secret" and
2391
value != old_client_settings[client_name]
2393
client[name] = value
2397
# Clients who has passed its expire date can still be
2398
# enabled if its last checker was successful. Clients
2399
# whose checker succeeded before we stored its state is
2400
# assumed to have successfully run all checkers during
2402
if client["enabled"]:
2403
if datetime.datetime.utcnow() >= client["expires"]:
2404
if not client["last_checked_ok"]:
2406
"disabling client {0} - Client never "
2407
"performed a successful checker"
2408
.format(client_name))
2409
client["enabled"] = False
2410
elif client["last_checker_status"] != 0:
2412
"disabling client {0} - Client "
2413
"last checker failed with error code {1}"
2414
.format(client_name,
2415
client["last_checker_status"]))
2416
client["enabled"] = False
2418
client["expires"] = (datetime.datetime
2420
+ client["timeout"])
2421
logger.debug("Last checker succeeded,"
2422
" keeping {0} enabled"
2423
.format(client_name))
2425
client["secret"] = (
2426
pgp.decrypt(client["encrypted_secret"],
2427
client_settings[client_name]
2430
# If decryption fails, we use secret from new settings
2431
logger.debug("Failed to decrypt {0} old secret"
2432
.format(client_name))
2433
client["secret"] = (
2434
client_settings[client_name]["secret"])
2436
# Add/remove clients based on new changes made to config
2437
for client_name in (set(old_client_settings)
2438
- set(client_settings)):
2439
del clients_data[client_name]
2440
for client_name in (set(client_settings)
2441
- set(old_client_settings)):
2442
clients_data[client_name] = client_settings[client_name]
2444
# Create all client objects
2445
for client_name, client in clients_data.iteritems():
2446
tcp_server.clients[client_name] = client_class(
2447
name = client_name, settings = client)
2449
if not tcp_server.clients:
2450
logger.warning("No clients defined")
822
def remove_from_clients(client):
823
clients.remove(client)
825
logger.critical(u"No clients left, exiting")
828
clients.update(Set(Client(name = section,
829
stop_hook = remove_from_clients,
831
= dict(client_config.items(section)))
832
for section in client_config.sections()))
834
logger.critical(u"No clients defined")
838
# Redirect stdin so all checkers get /dev/null
839
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
840
os.dup2(null, sys.stdin.fileno())
845
logger.removeHandler(console)
846
# Close all input and output, do double fork, etc.
851
pidfile.write(str(pid) + "\n")
855
logger.error(u"Could not write to file %r with PID %d",
858
# "pidfile" was never created
863
"Cleanup function; run on exit"
865
# From the Avahi example code
866
if not group is None:
869
# End of Avahi example code
872
client = clients.pop()
873
client.stop_hook = None
876
atexit.register(cleanup)
2456
pidfile.write(str(pid) + "\n".encode("utf-8"))
2459
logger.error("Could not write to file %r with PID %d",
2462
# "pidfile" was never created
2465
879
signal.signal(signal.SIGINT, signal.SIG_IGN)
2467
880
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2468
881
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2471
@alternate_dbus_interfaces({"se.recompile.Mandos":
2472
"se.bsnet.fukt.Mandos"})
2473
class MandosDBusService(DBusObjectWithProperties):
2474
"""A D-Bus proxy object"""
2476
dbus.service.Object.__init__(self, bus, "/")
2477
_interface = "se.recompile.Mandos"
2479
@dbus_interface_annotations(_interface)
2481
return { "org.freedesktop.DBus.Property"
2482
".EmitsChangedSignal":
2485
@dbus.service.signal(_interface, signature="o")
2486
def ClientAdded(self, objpath):
2490
@dbus.service.signal(_interface, signature="ss")
2491
def ClientNotFound(self, fingerprint, address):
2495
@dbus.service.signal(_interface, signature="os")
2496
def ClientRemoved(self, objpath, name):
2500
@dbus.service.method(_interface, out_signature="ao")
2501
def GetAllClients(self):
2503
return dbus.Array(c.dbus_object_path
2505
tcp_server.clients.itervalues())
2507
@dbus.service.method(_interface,
2508
out_signature="a{oa{sv}}")
2509
def GetAllClientsWithProperties(self):
2511
return dbus.Dictionary(
2512
((c.dbus_object_path, c.GetAll(""))
2513
for c in tcp_server.clients.itervalues()),
2516
@dbus.service.method(_interface, in_signature="o")
2517
def RemoveClient(self, object_path):
2519
for c in tcp_server.clients.itervalues():
2520
if c.dbus_object_path == object_path:
2521
del tcp_server.clients[c.name]
2522
c.remove_from_connection()
2523
# Don't signal anything except ClientRemoved
2524
c.disable(quiet=True)
2526
self.ClientRemoved(object_path, c.name)
2528
raise KeyError(object_path)
2532
mandos_dbus_service = MandosDBusService()
2535
"Cleanup function; run on exit"
2538
multiprocessing.active_children()
2539
if not (tcp_server.clients or client_settings):
2542
# Store client before exiting. Secrets are encrypted with key
2543
# based on what config file has. If config file is
2544
# removed/edited, old secret will thus be unrecovable.
2546
with PGPEngine() as pgp:
2547
for client in tcp_server.clients.itervalues():
2548
key = client_settings[client.name]["secret"]
2549
client.encrypted_secret = pgp.encrypt(client.secret,
2553
# A list of attributes that can not be pickled
2555
exclude = set(("bus", "changedstate", "secret",
2557
for name, typ in (inspect.getmembers
2558
(dbus.service.Object)):
2561
client_dict["encrypted_secret"] = (client
2563
for attr in client.client_structure:
2564
if attr not in exclude:
2565
client_dict[attr] = getattr(client, attr)
2567
clients[client.name] = client_dict
2568
del client_settings[client.name]["secret"]
2571
with (tempfile.NamedTemporaryFile
2572
(mode='wb', suffix=".pickle", prefix='clients-',
2573
dir=os.path.dirname(stored_state_path),
2574
delete=False)) as stored_state:
2575
pickle.dump((clients, client_settings), stored_state)
2576
tempname=stored_state.name
2577
os.rename(tempname, stored_state_path)
2578
except (IOError, OSError) as e:
2584
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2585
logger.warning("Could not save persistent state: {0}"
2586
.format(os.strerror(e.errno)))
2588
logger.warning("Could not save persistent state:",
2592
# Delete all clients, and settings from config
2593
while tcp_server.clients:
2594
name, client = tcp_server.clients.popitem()
2596
client.remove_from_connection()
2597
# Don't signal anything except ClientRemoved
2598
client.disable(quiet=True)
2601
mandos_dbus_service.ClientRemoved(client
2604
client_settings.clear()
2606
atexit.register(cleanup)
2608
for client in tcp_server.clients.itervalues():
2611
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2612
# Need to initiate checking of clients
2614
client.init_checker()
883
for client in clients:
2616
886
tcp_server.enable()
2617
887
tcp_server.server_activate()
2619
889
# Find out what port we got
2620
890
service.port = tcp_server.socket.getsockname()[1]
2622
logger.info("Now listening on address %r, port %d,"
2623
" flowinfo %d, scope_id %d",
2624
*tcp_server.socket.getsockname())
2626
logger.info("Now listening on address %r, port %d",
2627
*tcp_server.socket.getsockname())
891
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
892
u" scope_id %d" % tcp_server.socket.getsockname())
2629
894
#service.interface = tcp_server.socket.getsockname()[3]
2632
897
# From the Avahi example code
898
server.connect_to_signal("StateChanged", server_state_changed)
2635
except dbus.exceptions.DBusException as error:
2636
logger.critical("D-Bus Exception", exc_info=error)
900
server_state_changed(server.GetState())
901
except dbus.exceptions.DBusException, error:
902
logger.critical(u"DBusException: %s", error)
2639
904
# End of Avahi example code
2641
906
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2642
907
lambda *args, **kwargs:
2643
(tcp_server.handle_request
2644
(*args[2:], **kwargs) or True))
908
tcp_server.handle_request\
909
(*args[2:], **kwargs) or True)
2646
logger.debug("Starting main loop")
911
logger.debug(u"Starting main loop")
2648
except AvahiError as error:
2649
logger.critical("Avahi Error", exc_info=error)
913
except AvahiError, error:
914
logger.critical(u"AvahiError: %s" + unicode(error))
2652
916
except KeyboardInterrupt:
2654
print("", file=sys.stderr)
2655
logger.debug("Server received KeyboardInterrupt")
2656
logger.debug("Server exiting")
2657
# Must run before the D-Bus bus name gets deregistered
2660
920
if __name__ == '__main__':