241
106
max_renames: integer; maximum number of renames
242
107
rename_count: integer; counter so we only rename after collisions
243
108
a sensible number of times
244
group: D-Bus Entry Group
246
bus: dbus.SystemBus()
249
110
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):
111
type = None, port = None, TXT = None, domain = "",
112
host = "", max_renames = 32768):
253
113
self.interface = interface
255
self.type = servicetype
257
self.TXT = TXT if TXT is not None else []
258
121
self.domain = domain
260
123
self.rename_count = 0
261
124
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
125
def rename(self):
269
126
"""Derived from the Avahi example code"""
270
127
if self.rename_count >= self.max_renames:
271
logger.critical("No suitable Zeroconf service name found"
272
" after %i retries, exiting.",
128
logger.critical(u"No suitable Zeroconf service name found"
129
u" after %i retries, exiting.",
274
131
raise AvahiServiceError("Too many renames")
275
self.name = unicode(self.server
276
.GetAlternativeServiceName(self.name))
277
logger.info("Changing Zeroconf service name to %r ...",
132
self.name = server.GetAlternativeServiceName(self.name)
133
logger.info(u"Changing Zeroconf service name to %r ...",
135
syslogger.setFormatter(logging.Formatter\
136
('Mandos (%s): %%(levelname)s:'
137
' %%(message)s' % self.name))
282
except dbus.exceptions.DBusException as error:
283
logger.critical("D-Bus Exception", exc_info=error)
286
140
self.rename_count += 1
288
141
def remove(self):
289
142
"""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:
143
if group is not None:
297
146
"""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))
149
group = dbus.Interface\
150
(bus.get_object(avahi.DBUS_NAME,
151
server.EntryGroupNew()),
152
avahi.DBUS_INTERFACE_ENTRY_GROUP)
153
group.connect_to_signal('StateChanged',
154
entry_group_state_changed)
155
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
156
service.name, service.type)
158
self.interface, # interface
159
avahi.PROTO_INET6, # protocol
160
dbus.UInt32(0), # flags
161
self.name, self.type,
162
self.domain, self.host,
163
dbus.UInt16(self.port),
164
avahi.string_array_to_txt_array(self.TXT))
167
# From the Avahi example code:
168
group = None # our entry group
169
# End of Avahi example code
398
172
class Client(object):
399
173
"""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
175
name: string; from the config file, used in log messages
176
fingerprint: string (40 or 32 hexadecimal digits); used to
177
uniquely identify the client
178
secret: bytestring; sent verbatim (over TLS) to client
179
host: string; available for use by the checker command
180
created: datetime.datetime(); object creation, not client host
181
last_checked_ok: datetime.datetime() or None if not yet checked OK
182
timeout: datetime.timedelta(); How long from last_checked_ok
183
until this client is invalid
184
interval: datetime.timedelta(); How often to start a new checker
185
stop_hook: If set, called by stop() as stop_hook(self)
186
checker: subprocess.Popen(); a running checker process used
187
to see if the client lives.
188
'None' if no process is running.
189
checker_initiator_tag: a gobject event source tag, or None
190
stop_initiator_tag: - '' -
191
checker_callback_tag: - '' -
192
checker_command: string; External command which is run to check if
193
client lives. %() expansions are done at
411
194
runtime with vars(self) as dict, so that for
412
195
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
197
_timeout: Real variable for 'timeout'
198
_interval: Real variable for 'interval'
199
_timeout_milliseconds: Used when calling gobject.timeout_add()
200
_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):
202
def _set_timeout(self, timeout):
203
"Setter function for 'timeout' attribute"
204
self._timeout = timeout
205
self._timeout_milliseconds = ((self.timeout.days
206
* 24 * 60 * 60 * 1000)
207
+ (self.timeout.seconds * 1000)
208
+ (self.timeout.microseconds
210
timeout = property(lambda self: self._timeout,
213
def _set_interval(self, interval):
214
"Setter function for 'interval' attribute"
215
self._interval = interval
216
self._interval_milliseconds = ((self.interval.days
217
* 24 * 60 * 60 * 1000)
218
+ (self.interval.seconds
220
+ (self.interval.microseconds
222
interval = property(lambda self: self._interval,
225
def __init__(self, name = None, stop_hook=None, config={}):
226
"""Note: the 'checker' key in 'config' sets the
227
'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)
230
logger.debug(u"Creating client %r", self.name)
535
231
# Uppercase and remove spaces from fingerprint for later
536
232
# 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
234
self.fingerprint = config["fingerprint"].upper()\
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
237
if "secret" in config:
238
self.secret = config["secret"].decode(u"base64")
239
elif "secfile" in config:
240
sf = open(config["secfile"])
241
self.secret = sf.read()
244
raise TypeError(u"No secret or secfile for client %s"
246
self.host = config.get("host", "")
247
self.created = datetime.datetime.now()
248
self.last_checked_ok = None
249
self.timeout = string_to_delta(config["timeout"])
250
self.interval = string_to_delta(config["interval"])
251
self.stop_hook = stop_hook
543
252
self.checker = None
544
253
self.checker_initiator_tag = None
545
self.disable_initiator_tag = None
254
self.stop_initiator_tag = None
546
255
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()
256
self.check_command = config["checker"]
571
258
"""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
259
# Schedule a new checker to be started an 'interval' from now,
606
260
# 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(),
261
self.checker_initiator_tag = gobject.timeout_add\
262
(self._interval_milliseconds,
618
264
# Also start a new checker *right now*.
619
265
self.start_checker()
621
def checker_callback(self, pid, condition, command):
266
# Schedule a stop() when 'timeout' has passed
267
self.stop_initiator_tag = gobject.timeout_add\
268
(self._timeout_milliseconds,
272
The possibility that a client might be restarted is left open,
273
but not currently used."""
274
# If this client doesn't have a secret, it is already stopped.
275
if hasattr(self, "secret") and self.secret:
276
logger.info(u"Stopping client %s", self.name)
280
if getattr(self, "stop_initiator_tag", False):
281
gobject.source_remove(self.stop_initiator_tag)
282
self.stop_initiator_tag = None
283
if getattr(self, "checker_initiator_tag", False):
284
gobject.source_remove(self.checker_initiator_tag)
285
self.checker_initiator_tag = None
289
# Do not run this again if called by a gobject.timeout_add
292
self.stop_hook = None
294
def checker_callback(self, pid, condition):
622
295
"""The checker has completed, so take appropriate actions."""
296
now = datetime.datetime.now()
623
297
self.checker_callback_tag = None
624
298
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?",
299
if os.WIFEXITED(condition) \
300
and (os.WEXITSTATUS(condition) == 0):
301
logger.info(u"Checker for %(name)s succeeded",
303
self.last_checked_ok = now
304
gobject.source_remove(self.stop_initiator_tag)
305
self.stop_initiator_tag = gobject.timeout_add\
306
(self._timeout_milliseconds,
308
elif not os.WIFEXITED(condition):
309
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()
312
logger.info(u"Checker for %(name)s failed",
661
314
def start_checker(self):
662
315
"""Start a new checker subprocess if one is not running.
664
316
If a checker already exists, leave it running and do
666
318
# 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
319
# did that, then if a checker (for some reason) started
320
# running slowly and taking more than 'interval' time, the
321
# client would inevitably timeout, since no checker would get
322
# a chance to run to completion. If we instead leave running
671
323
# 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
324
# than 'timeout' for the client to be declared invalid, which
325
# is as it should be.
689
326
if self.checker is None:
691
# In case checker_command has exactly one % operator
692
command = self.checker_command % self.host
328
# In case check_command has exactly one % operator
329
command = self.check_command % self.host
693
330
except TypeError:
694
331
# Escape attributes for the shell
695
escaped_attrs = dict(
697
re.escape(unicode(str(getattr(self, attr, "")),
701
self.runtime_expansions)
332
escaped_attrs = dict((key, re.escape(str(val)))
334
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)
336
command = self.check_command % escaped_attrs
337
except TypeError, error:
338
logger.error(u'Could not format string "%s":'
339
u' %s', self.check_command, error)
708
340
return True # Try again later
709
self.current_checker_command = command
711
logger.info("Starting checker %r for %s",
342
logger.info(u"Starting checker %r for %s",
712
343
command, self.name)
713
344
# We don't need to redirect stdout and stderr, since
714
345
# in normal mode, that is already done by daemon(),
740
363
self.checker_callback_tag = None
741
364
if getattr(self, "checker", None) is None:
743
logger.debug("Stopping checker for %(name)s", vars(self))
366
logger.debug(u"Stopping checker for %(name)s", vars(self))
745
self.checker.terminate()
368
os.kill(self.checker.pid, signal.SIGTERM)
747
370
#if self.checker.poll() is None:
748
# self.checker.kill()
749
except OSError as error:
371
# os.kill(self.checker.pid, signal.SIGKILL)
372
except OSError, error:
750
373
if error.errno != errno.ESRCH: # No such process
752
375
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.
376
def still_valid(self):
377
"""Has the timeout not yet passed for this client?"""
378
now = datetime.datetime.now()
379
if self.last_checked_ok is None:
380
return now < (self.created + self.timeout)
382
return now < (self.last_checked_ok + self.timeout)
385
def peer_certificate(session):
386
"Return the peer's OpenPGP certificate as a bytestring"
387
# If not an OpenPGP certificate...
388
if gnutls.library.functions.gnutls_certificate_type_get\
389
(session._c_object) \
390
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
391
# ...do the normal thing
392
return session.peer_certificate
393
list_size = ctypes.c_uint()
394
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
395
(session._c_object, ctypes.byref(list_size))
396
if list_size.value == 0:
399
return ctypes.string_at(cert.data, cert.size)
402
def fingerprint(openpgp):
403
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
404
# New GnuTLS "datum" with the OpenPGP public key
405
datum = gnutls.library.types.gnutls_datum_t\
406
(ctypes.cast(ctypes.c_char_p(openpgp),
407
ctypes.POINTER(ctypes.c_ubyte)),
408
ctypes.c_uint(len(openpgp)))
409
# New empty GnuTLS certificate
410
crt = gnutls.library.types.gnutls_openpgp_crt_t()
411
gnutls.library.functions.gnutls_openpgp_crt_init\
413
# Import the OpenPGP public key into the certificate
414
gnutls.library.functions.gnutls_openpgp_crt_import\
415
(crt, ctypes.byref(datum),
416
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
417
# Verify the self signature in the key
418
crtverify = ctypes.c_uint();
419
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
420
(crt, 0, ctypes.byref(crtverify))
421
if crtverify.value != 0:
422
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
423
raise gnutls.errors.CertificateSecurityError("Verify failed")
424
# New buffer for the fingerprint
425
buffer = ctypes.create_string_buffer(20)
426
buffer_length = ctypes.c_size_t()
427
# Get the fingerprint from the certificate into the buffer
428
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
429
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
430
# Deinit the certificate
431
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
432
# Convert the buffer to a Python bytestring
433
fpr = ctypes.string_at(buffer, buffer_length.value)
434
# Convert the bytestring to hexadecimal notation
435
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
439
class tcp_handler(SocketServer.BaseRequestHandler, object):
440
"""A TCP request handler class.
441
Instantiated by IPv6_TCPServer for each request to handle it.
1643
442
Note: This will run in its own forked process."""
1645
444
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
445
logger.info(u"TCP connection from: %s",
446
unicode(self.client_address))
447
session = gnutls.connection.ClientSession\
448
(self.request, gnutls.connection.X509Credentials())
450
line = self.request.makefile().readline()
451
logger.debug(u"Protocol version: %r", line)
453
if int(line.strip().split()[0]) > 1:
455
except (ValueError, IndexError, RuntimeError), error:
456
logger.error(u"Unknown protocol version: %s", error)
459
# Note: gnutls.connection.X509Credentials is really a generic
460
# GnuTLS certificate credentials object so long as no X.509
461
# keys are added to it. Therefore, we can use it here despite
462
# using OpenPGP certificates.
464
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
465
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
467
priority = "NORMAL" # Fallback default, since this
469
if self.server.settings["priority"]:
470
priority = self.server.settings["priority"]
471
gnutls.library.functions.gnutls_priority_set_direct\
472
(session._c_object, priority, None);
476
except gnutls.errors.GNUTLSError, error:
477
logger.warning(u"Handshake failed: %s", error)
478
# Do not run session.bye() here: the session is not
479
# established. Just abandon the request.
482
fpr = fingerprint(peer_certificate(session))
483
except (TypeError, gnutls.errors.GNUTLSError), error:
484
logger.warning(u"Bad certificate: %s", error)
487
logger.debug(u"Fingerprint: %s", fpr)
489
for c in self.server.clients:
490
if c.fingerprint == fpr:
494
logger.warning(u"Client not found for fingerprint: %s",
498
# Have to check if client.still_valid(), since it is possible
499
# that the client timed out while establishing the GnuTLS
501
if not client.still_valid():
502
logger.warning(u"Client %(name)s is invalid",
507
while sent_size < len(client.secret):
508
sent = session.send(client.secret[sent_size:])
509
logger.debug(u"Sent: %d, remaining: %d",
510
sent, len(client.secret)
511
- (sent_size + sent))
516
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
517
"""IPv6 TCP server. Accepts 'None' as address and/or port.
519
settings: Server settings
520
clients: Set() of Client objects
1901
521
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)
523
address_family = socket.AF_INET6
524
def __init__(self, *args, **kwargs):
525
if "settings" in kwargs:
526
self.settings = kwargs["settings"]
527
del kwargs["settings"]
528
if "clients" in kwargs:
529
self.clients = kwargs["clients"]
530
del kwargs["clients"]
532
return super(type(self), self).__init__(*args, **kwargs)
1912
533
def server_bind(self):
1913
534
"""This overrides the normal server_bind() function
1914
535
to bind to an interface if one was specified, and also NOT to
1915
536
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",
537
if self.settings["interface"]:
538
# 25 is from /usr/include/asm-i486/socket.h
539
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
541
self.socket.setsockopt(socket.SOL_SOCKET,
543
self.settings["interface"])
544
except socket.error, error:
545
if error[0] == errno.EPERM:
546
logger.error(u"No permission to"
547
u" bind to interface %s",
548
self.settings["interface"])
1938
551
# Only bind(2) the socket if we really need to.
1939
552
if self.server_address[0] or self.server_address[1]:
1940
553
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,
555
self.server_address = (in6addr_any,
1946
556
self.server_address[1])
1947
557
elif not self.server_address[1]:
1948
558
self.server_address = (self.server_address[0],
1950
# if self.interface:
560
# if self.settings["interface"]:
1951
561
# self.server_address = (self.server_address[0],
1954
564
# 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)
567
return super(type(self), self).server_bind()
1982
568
def server_activate(self):
1983
569
if self.enabled:
1984
return socketserver.TCPServer.server_activate(self)
570
return super(type(self), self).server_activate()
1986
571
def enable(self):
1987
572
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
575
def string_to_delta(interval):
2066
576
"""Parse a string and return a datetime.timedelta
2068
578
>>> string_to_delta('7d')
2069
579
datetime.timedelta(7)
2070
580
>>> string_to_delta('60s')
2184
722
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2185
723
"servicename": "Mandos",
2190
"statedir": "/var/lib/mandos"
2193
726
# Parse config file for server-global settings
2194
server_config = configparser.SafeConfigParser(server_defaults)
727
server_config = ConfigParser.SafeConfigParser(server_defaults)
2195
728
del server_defaults
2196
server_config.read(os.path.join(options.configdir,
729
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2198
730
# Convert the SafeConfigParser object to a dict
2199
731
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",
732
# Use getboolean on the boolean config option
733
server_settings["debug"] = server_config.getboolean\
2207
735
del server_config
2209
737
# Override the settings from the config file with command line
2210
738
# options, if set.
2211
739
for option in ("interface", "address", "port", "debug",
2212
"priority", "servicename", "configdir",
2213
"use_dbus", "use_ipv6", "debuglevel", "restore",
740
"priority", "servicename", "configdir"):
2215
741
value = getattr(options, option)
2216
742
if value is not None:
2217
743
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
745
# Now we have our good server settings in "server_settings"
2225
##################################################################
2228
747
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)
750
syslogger.setLevel(logging.WARNING)
751
console.setLevel(logging.WARNING)
2244
753
if server_settings["servicename"] != "Mandos":
2245
syslogger.setFormatter(logging.Formatter
2246
('Mandos ({0}) [%(process)d]:'
2247
' %(levelname)s: %(message)s'
2248
.format(server_settings
754
syslogger.setFormatter(logging.Formatter\
755
('Mandos (%s): %%(levelname)s:'
757
% server_settings["servicename"]))
2251
759
# Parse config file with clients
2252
client_config = configparser.SafeConfigParser(Client
760
client_defaults = { "timeout": "1h",
762
"checker": "fping -q -- %(host)s",
765
client_config = ConfigParser.SafeConfigParser(client_defaults)
2254
766
client_config.read(os.path.join(server_settings["configdir"],
2255
767
"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
770
tcp_server = IPv6_TCPServer((server_settings["address"],
771
server_settings["port"]),
773
settings=server_settings,
775
pidfilename = "/var/run/mandos.pid"
777
pidfile = open(pidfilename, "w")
778
except IOError, error:
779
logger.error("Could not open file %r", pidfilename)
784
uid = pwd.getpwnam("mandos").pw_uid
787
uid = pwd.getpwnam("nobody").pw_uid
791
gid = pwd.getpwnam("mandos").pw_gid
794
gid = pwd.getpwnam("nogroup").pw_gid
2290
except OSError as error:
800
except OSError, error:
2291
801
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()
805
service = AvahiService(name = server_settings["servicename"],
806
type = "_mandos._tcp", );
807
if server_settings["interface"]:
808
service.interface = if_nametoindex\
809
(server_settings["interface"])
2321
811
global main_loop
2322
814
# From the Avahi example code
2323
DBusGMainLoop(set_as_default=True)
815
DBusGMainLoop(set_as_default=True )
2324
816
main_loop = gobject.MainLoop()
2325
817
bus = dbus.SystemBus()
818
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
819
avahi.DBUS_PATH_SERVER),
820
avahi.DBUS_INTERFACE_SERVER)
2326
821
# 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")
823
def remove_from_clients(client):
824
clients.remove(client)
826
logger.critical(u"No clients left, exiting")
829
clients.update(Set(Client(name = section,
830
stop_hook = remove_from_clients,
832
= dict(client_config.items(section)))
833
for section in client_config.sections()))
835
logger.critical(u"No clients defined")
839
# Redirect stdin so all checkers get /dev/null
840
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
841
os.dup2(null, sys.stdin.fileno())
846
logger.removeHandler(console)
847
# Close all input and output, do double fork, etc.
852
pidfile.write(str(pid) + "\n")
856
logger.error(u"Could not write to file %r with PID %d",
859
# "pidfile" was never created
864
"Cleanup function; run on exit"
866
# From the Avahi example code
867
if not group is None:
870
# End of Avahi example code
873
client = clients.pop()
874
client.stop_hook = None
877
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
880
signal.signal(signal.SIGINT, signal.SIG_IGN)
2467
881
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2468
882
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()
884
for client in clients:
2616
887
tcp_server.enable()
2617
888
tcp_server.server_activate()
2619
890
# Find out what port we got
2620
891
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())
892
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
893
u" scope_id %d" % tcp_server.socket.getsockname())
2629
895
#service.interface = tcp_server.socket.getsockname()[3]
2632
898
# From the Avahi example code
899
server.connect_to_signal("StateChanged", server_state_changed)
2635
except dbus.exceptions.DBusException as error:
2636
logger.critical("D-Bus Exception", exc_info=error)
901
server_state_changed(server.GetState())
902
except dbus.exceptions.DBusException, error:
903
logger.critical(u"DBusException: %s", error)
2639
905
# End of Avahi example code
2641
907
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2642
908
lambda *args, **kwargs:
2643
(tcp_server.handle_request
2644
(*args[2:], **kwargs) or True))
909
tcp_server.handle_request\
910
(*args[2:], **kwargs) or True)
2646
logger.debug("Starting main loop")
912
logger.debug(u"Starting main loop")
913
main_loop_started = True
2648
except AvahiError as error:
2649
logger.critical("Avahi Error", exc_info=error)
915
except AvahiError, error:
916
logger.critical(u"AvahiError: %s" + unicode(error))
2652
918
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
922
if __name__ == '__main__':