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())
383
class AvahiServiceToSyslog(AvahiService):
385
"""Add the new name to the syslog messages"""
386
ret = AvahiService.rename(self)
387
syslogger.setFormatter(logging.Formatter
388
('Mandos ({0}) [%(process)d]:'
389
' %(levelname)s: %(message)s'
394
def timedelta_to_milliseconds(td):
395
"Convert a datetime.timedelta() to milliseconds"
396
return ((td.days * 24 * 60 * 60 * 1000)
397
+ (td.seconds * 1000)
398
+ (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
401
172
class Client(object):
402
173
"""A representation of a client host served by this server.
405
approved: bool(); 'None' if not yet approved/disapproved
406
approval_delay: datetime.timedelta(); Time to wait for approval
407
approval_duration: datetime.timedelta(); Duration of one approval
408
checker: subprocess.Popen(); a running checker process used
409
to see if the client lives.
410
'None' if no process is running.
411
checker_callback_tag: a gobject event source tag, or None
412
checker_command: string; External command which is run to check
413
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
414
194
runtime with vars(self) as dict, so that for
415
195
instance %(name)s can be used in the command.
416
checker_initiator_tag: a gobject event source tag, or None
417
created: datetime.datetime(); (UTC) object creation
418
client_structure: Object describing what attributes a client has
419
and is used for storing the client at exit
420
current_checker_command: string; current running checker_command
421
disable_initiator_tag: a gobject event source tag, or None
423
fingerprint: string (40 or 32 hexadecimal digits); used to
424
uniquely identify the client
425
host: string; available for use by the checker command
426
interval: datetime.timedelta(); How often to start a new checker
427
last_approval_request: datetime.datetime(); (UTC) or None
428
last_checked_ok: datetime.datetime(); (UTC) or None
429
last_checker_status: integer between 0 and 255 reflecting exit
430
status of last checker. -1 reflects crashed
431
checker, -2 means no checker completed yet.
432
last_enabled: datetime.datetime(); (UTC) or None
433
name: string; from the config file, used in log messages and
435
secret: bytestring; sent verbatim (over TLS) to client
436
timeout: datetime.timedelta(); How long from last_checked_ok
437
until this client is disabled
438
extended_timeout: extra long timeout when secret has been sent
439
runtime_expansions: Allowed attributes for runtime expansion.
440
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: - '' -
444
runtime_expansions = ("approval_delay", "approval_duration",
445
"created", "enabled", "fingerprint",
446
"host", "interval", "last_checked_ok",
447
"last_enabled", "name", "timeout")
448
client_defaults = { "timeout": "5m",
449
"extended_timeout": "15m",
451
"checker": "fping -q -- %%(host)s",
453
"approval_delay": "0s",
454
"approval_duration": "1s",
455
"approved_by_default": "True",
459
def timeout_milliseconds(self):
460
"Return the 'timeout' attribute in milliseconds"
461
return timedelta_to_milliseconds(self.timeout)
463
def extended_timeout_milliseconds(self):
464
"Return the 'extended_timeout' attribute in milliseconds"
465
return timedelta_to_milliseconds(self.extended_timeout)
467
def interval_milliseconds(self):
468
"Return the 'interval' attribute in milliseconds"
469
return timedelta_to_milliseconds(self.interval)
471
def approval_delay_milliseconds(self):
472
return timedelta_to_milliseconds(self.approval_delay)
475
def config_parser(config):
476
"""Construct a new dict of client settings of this form:
477
{ client_name: {setting_name: value, ...}, ...}
478
with exceptions for any special settings as defined above.
479
NOTE: Must be a pure function. Must return the same result
480
value given the same arguments.
483
for client_name in config.sections():
484
section = dict(config.items(client_name))
485
client = settings[client_name] = {}
487
client["host"] = section["host"]
488
# Reformat values from string types to Python types
489
client["approved_by_default"] = config.getboolean(
490
client_name, "approved_by_default")
491
client["enabled"] = config.getboolean(client_name,
494
client["fingerprint"] = (section["fingerprint"].upper()
496
if "secret" in section:
497
client["secret"] = section["secret"].decode("base64")
498
elif "secfile" in section:
499
with open(os.path.expanduser(os.path.expandvars
500
(section["secfile"])),
502
client["secret"] = secfile.read()
504
raise TypeError("No secret or secfile for section {0}"
506
client["timeout"] = string_to_delta(section["timeout"])
507
client["extended_timeout"] = string_to_delta(
508
section["extended_timeout"])
509
client["interval"] = string_to_delta(section["interval"])
510
client["approval_delay"] = string_to_delta(
511
section["approval_delay"])
512
client["approval_duration"] = string_to_delta(
513
section["approval_duration"])
514
client["checker_command"] = section["checker"]
515
client["last_approval_request"] = None
516
client["last_checked_ok"] = None
517
client["last_checker_status"] = -2
521
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'
523
# adding all client settings
524
for setting, value in settings.iteritems():
525
setattr(self, setting, value)
528
if not hasattr(self, "last_enabled"):
529
self.last_enabled = datetime.datetime.utcnow()
530
if not hasattr(self, "expires"):
531
self.expires = (datetime.datetime.utcnow()
534
self.last_enabled = None
537
logger.debug("Creating client %r", self.name)
230
logger.debug(u"Creating client %r", self.name)
538
231
# Uppercase and remove spaces from fingerprint for later
539
232
# comparison purposes with return value from the fingerprint()
541
logger.debug(" Fingerprint: %s", self.fingerprint)
542
self.created = settings.get("created",
543
datetime.datetime.utcnow())
545
# 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
546
252
self.checker = None
547
253
self.checker_initiator_tag = None
548
self.disable_initiator_tag = None
254
self.stop_initiator_tag = None
549
255
self.checker_callback_tag = None
550
self.current_checker_command = None
552
self.approvals_pending = 0
553
self.changedstate = (multiprocessing_manager
554
.Condition(multiprocessing_manager
556
self.client_structure = [attr for attr in
557
self.__dict__.iterkeys()
558
if not attr.startswith("_")]
559
self.client_structure.append("client_structure")
561
for name, t in inspect.getmembers(type(self),
565
if not name.startswith("_"):
566
self.client_structure.append(name)
568
# Send notice to process children that client state has changed
569
def send_changedstate(self):
570
with self.changedstate:
571
self.changedstate.notify_all()
256
self.check_command = config["checker"]
574
258
"""Start this client's checker and timeout hooks"""
575
if getattr(self, "enabled", False):
578
self.expires = datetime.datetime.utcnow() + self.timeout
580
self.last_enabled = datetime.datetime.utcnow()
582
self.send_changedstate()
584
def disable(self, quiet=True):
585
"""Disable this client."""
586
if not getattr(self, "enabled", False):
589
logger.info("Disabling client %s", self.name)
590
if getattr(self, "disable_initiator_tag", None) is not None:
591
gobject.source_remove(self.disable_initiator_tag)
592
self.disable_initiator_tag = None
594
if getattr(self, "checker_initiator_tag", None) is not None:
595
gobject.source_remove(self.checker_initiator_tag)
596
self.checker_initiator_tag = None
600
self.send_changedstate()
601
# Do not run this again if called by a gobject.timeout_add
607
def init_checker(self):
608
259
# Schedule a new checker to be started an 'interval' from now,
609
260
# and every interval from then on.
610
if self.checker_initiator_tag is not None:
611
gobject.source_remove(self.checker_initiator_tag)
612
self.checker_initiator_tag = (gobject.timeout_add
613
(self.interval_milliseconds(),
615
# Schedule a disable() when 'timeout' has passed
616
if self.disable_initiator_tag is not None:
617
gobject.source_remove(self.disable_initiator_tag)
618
self.disable_initiator_tag = (gobject.timeout_add
619
(self.timeout_milliseconds(),
261
self.checker_initiator_tag = gobject.timeout_add\
262
(self._interval_milliseconds,
621
264
# Also start a new checker *right now*.
622
265
self.start_checker()
624
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):
625
295
"""The checker has completed, so take appropriate actions."""
296
now = datetime.datetime.now()
626
297
self.checker_callback_tag = None
627
298
self.checker = None
628
if os.WIFEXITED(condition):
629
self.last_checker_status = os.WEXITSTATUS(condition)
630
if self.last_checker_status == 0:
631
logger.info("Checker for %(name)s succeeded",
635
logger.info("Checker for %(name)s failed",
638
self.last_checker_status = -1
639
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?",
642
def checked_ok(self):
643
"""Assert that the client has been seen, alive and well."""
644
self.last_checked_ok = datetime.datetime.utcnow()
645
self.last_checker_status = 0
648
def bump_timeout(self, timeout=None):
649
"""Bump up the timeout for this client."""
651
timeout = self.timeout
652
if self.disable_initiator_tag is not None:
653
gobject.source_remove(self.disable_initiator_tag)
654
self.disable_initiator_tag = None
655
if getattr(self, "enabled", False):
656
self.disable_initiator_tag = (gobject.timeout_add
657
(timedelta_to_milliseconds
658
(timeout), self.disable))
659
self.expires = datetime.datetime.utcnow() + timeout
661
def need_approval(self):
662
self.last_approval_request = datetime.datetime.utcnow()
312
logger.info(u"Checker for %(name)s failed",
664
314
def start_checker(self):
665
315
"""Start a new checker subprocess if one is not running.
667
316
If a checker already exists, leave it running and do
669
318
# The reason for not killing a running checker is that if we
670
# did that, and if a checker (for some reason) started running
671
# slowly and taking more than 'interval' time, then the client
672
# would inevitably timeout, since no checker would get a
673
# 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
674
323
# checkers alone, the checker would have to take more time
675
# than 'timeout' for the client to be disabled, which is as it
678
# If a checker exists, make sure it is not a zombie
680
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
681
except (AttributeError, OSError) as error:
682
if (isinstance(error, OSError)
683
and error.errno != errno.ECHILD):
687
logger.warning("Checker was a zombie")
688
gobject.source_remove(self.checker_callback_tag)
689
self.checker_callback(pid, status,
690
self.current_checker_command)
691
# Start a new checker if needed
324
# than 'timeout' for the client to be declared invalid, which
325
# is as it should be.
692
326
if self.checker is None:
694
# In case checker_command has exactly one % operator
695
command = self.checker_command % self.host
328
# In case check_command has exactly one % operator
329
command = self.check_command % self.host
696
330
except TypeError:
697
331
# Escape attributes for the shell
698
escaped_attrs = dict(
700
re.escape(unicode(str(getattr(self, attr, "")),
704
self.runtime_expansions)
332
escaped_attrs = dict((key, re.escape(str(val)))
334
vars(self).iteritems())
707
command = self.checker_command % escaped_attrs
708
except TypeError as error:
709
logger.error('Could not format string "%s"',
710
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)
711
340
return True # Try again later
712
self.current_checker_command = command
714
logger.info("Starting checker %r for %s",
342
logger.info(u"Starting checker %r for %s",
715
343
command, self.name)
716
344
# We don't need to redirect stdout and stderr, since
717
345
# in normal mode, that is already done by daemon(),
743
363
self.checker_callback_tag = None
744
364
if getattr(self, "checker", None) is None:
746
logger.debug("Stopping checker for %(name)s", vars(self))
366
logger.debug(u"Stopping checker for %(name)s", vars(self))
748
self.checker.terminate()
368
os.kill(self.checker.pid, signal.SIGTERM)
750
370
#if self.checker.poll() is None:
751
# self.checker.kill()
752
except OSError as error:
371
# os.kill(self.checker.pid, signal.SIGKILL)
372
except OSError, error:
753
373
if error.errno != errno.ESRCH: # No such process
755
375
self.checker = None
758
def dbus_service_property(dbus_interface, signature="v",
759
access="readwrite", byte_arrays=False):
760
"""Decorators for marking methods of a DBusObjectWithProperties to
761
become properties on the D-Bus.
763
The decorated method will be called with no arguments by "Get"
764
and with one argument by "Set".
766
The parameters, where they are supported, are the same as
767
dbus.service.method, except there is only "signature", since the
768
type from Get() and the type sent to Set() is the same.
770
# Encoding deeply encoded byte arrays is not supported yet by the
771
# "Set" method, so we fail early here:
772
if byte_arrays and signature != "ay":
773
raise ValueError("Byte arrays not supported for non-'ay'"
774
" signature {0!r}".format(signature))
776
func._dbus_is_property = True
777
func._dbus_interface = dbus_interface
778
func._dbus_signature = signature
779
func._dbus_access = access
780
func._dbus_name = func.__name__
781
if func._dbus_name.endswith("_dbus_property"):
782
func._dbus_name = func._dbus_name[:-14]
783
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
788
def dbus_interface_annotations(dbus_interface):
789
"""Decorator for marking functions returning interface annotations
793
@dbus_interface_annotations("org.example.Interface")
794
def _foo(self): # Function name does not matter
795
return {"org.freedesktop.DBus.Deprecated": "true",
796
"org.freedesktop.DBus.Property.EmitsChangedSignal":
800
func._dbus_is_interface = True
801
func._dbus_interface = dbus_interface
802
func._dbus_name = dbus_interface
807
def dbus_annotations(annotations):
808
"""Decorator to annotate D-Bus methods, signals or properties
811
@dbus_service_property("org.example.Interface", signature="b",
813
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
814
"org.freedesktop.DBus.Property."
815
"EmitsChangedSignal": "false"})
816
def Property_dbus_property(self):
817
return dbus.Boolean(False)
820
func._dbus_annotations = annotations
825
class DBusPropertyException(dbus.exceptions.DBusException):
826
"""A base class for D-Bus property-related exceptions
828
def __unicode__(self):
829
return unicode(str(self))
832
class DBusPropertyAccessException(DBusPropertyException):
833
"""A property's access permissions disallows an operation.
838
class DBusPropertyNotFound(DBusPropertyException):
839
"""An attempt was made to access a non-existing property.
844
class DBusObjectWithProperties(dbus.service.Object):
845
"""A D-Bus object with properties.
847
Classes inheriting from this can use the dbus_service_property
848
decorator to expose methods as D-Bus properties. It exposes the
849
standard Get(), Set(), and GetAll() methods on the D-Bus.
853
def _is_dbus_thing(thing):
854
"""Returns a function testing if an attribute is a D-Bus thing
856
If called like _is_dbus_thing("method") it returns a function
857
suitable for use as predicate to inspect.getmembers().
859
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
862
def _get_all_dbus_things(self, thing):
863
"""Returns a generator of (name, attribute) pairs
865
return ((getattr(athing.__get__(self), "_dbus_name",
867
athing.__get__(self))
868
for cls in self.__class__.__mro__
870
inspect.getmembers(cls,
871
self._is_dbus_thing(thing)))
873
def _get_dbus_property(self, interface_name, property_name):
874
"""Returns a bound method if one exists which is a D-Bus
875
property with the specified name and interface.
877
for cls in self.__class__.__mro__:
878
for name, value in (inspect.getmembers
880
self._is_dbus_thing("property"))):
881
if (value._dbus_name == property_name
882
and value._dbus_interface == interface_name):
883
return value.__get__(self)
886
raise DBusPropertyNotFound(self.dbus_object_path + ":"
887
+ interface_name + "."
890
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
892
def Get(self, interface_name, property_name):
893
"""Standard D-Bus property Get() method, see D-Bus standard.
895
prop = self._get_dbus_property(interface_name, property_name)
896
if prop._dbus_access == "write":
897
raise DBusPropertyAccessException(property_name)
899
if not hasattr(value, "variant_level"):
901
return type(value)(value, variant_level=value.variant_level+1)
903
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
904
def Set(self, interface_name, property_name, value):
905
"""Standard D-Bus property Set() method, see D-Bus standard.
907
prop = self._get_dbus_property(interface_name, property_name)
908
if prop._dbus_access == "read":
909
raise DBusPropertyAccessException(property_name)
910
if prop._dbus_get_args_options["byte_arrays"]:
911
# The byte_arrays option is not supported yet on
912
# signatures other than "ay".
913
if prop._dbus_signature != "ay":
915
value = dbus.ByteArray(b''.join(chr(byte)
919
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
920
out_signature="a{sv}")
921
def GetAll(self, interface_name):
922
"""Standard D-Bus property GetAll() method, see D-Bus
925
Note: Will not include properties with access="write".
928
for name, prop in self._get_all_dbus_things("property"):
930
and interface_name != prop._dbus_interface):
931
# Interface non-empty but did not match
933
# Ignore write-only properties
934
if prop._dbus_access == "write":
937
if not hasattr(value, "variant_level"):
938
properties[name] = value
940
properties[name] = type(value)(value, variant_level=
941
value.variant_level+1)
942
return dbus.Dictionary(properties, signature="sv")
944
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
946
path_keyword='object_path',
947
connection_keyword='connection')
948
def Introspect(self, object_path, connection):
949
"""Overloading of standard D-Bus method.
951
Inserts property tags and interface annotation tags.
953
xmlstring = dbus.service.Object.Introspect(self, object_path,
956
document = xml.dom.minidom.parseString(xmlstring)
957
def make_tag(document, name, prop):
958
e = document.createElement("property")
959
e.setAttribute("name", name)
960
e.setAttribute("type", prop._dbus_signature)
961
e.setAttribute("access", prop._dbus_access)
963
for if_tag in document.getElementsByTagName("interface"):
965
for tag in (make_tag(document, name, prop)
967
in self._get_all_dbus_things("property")
968
if prop._dbus_interface
969
== if_tag.getAttribute("name")):
970
if_tag.appendChild(tag)
971
# Add annotation tags
972
for typ in ("method", "signal", "property"):
973
for tag in if_tag.getElementsByTagName(typ):
975
for name, prop in (self.
976
_get_all_dbus_things(typ)):
977
if (name == tag.getAttribute("name")
978
and prop._dbus_interface
979
== if_tag.getAttribute("name")):
980
annots.update(getattr
984
for name, value in annots.iteritems():
985
ann_tag = document.createElement(
987
ann_tag.setAttribute("name", name)
988
ann_tag.setAttribute("value", value)
989
tag.appendChild(ann_tag)
990
# Add interface annotation tags
991
for annotation, value in dict(
992
itertools.chain.from_iterable(
993
annotations().iteritems()
994
for name, annotations in
995
self._get_all_dbus_things("interface")
996
if name == if_tag.getAttribute("name")
998
ann_tag = document.createElement("annotation")
999
ann_tag.setAttribute("name", annotation)
1000
ann_tag.setAttribute("value", value)
1001
if_tag.appendChild(ann_tag)
1002
# Add the names to the return values for the
1003
# "org.freedesktop.DBus.Properties" methods
1004
if (if_tag.getAttribute("name")
1005
== "org.freedesktop.DBus.Properties"):
1006
for cn in if_tag.getElementsByTagName("method"):
1007
if cn.getAttribute("name") == "Get":
1008
for arg in cn.getElementsByTagName("arg"):
1009
if (arg.getAttribute("direction")
1011
arg.setAttribute("name", "value")
1012
elif cn.getAttribute("name") == "GetAll":
1013
for arg in cn.getElementsByTagName("arg"):
1014
if (arg.getAttribute("direction")
1016
arg.setAttribute("name", "props")
1017
xmlstring = document.toxml("utf-8")
1019
except (AttributeError, xml.dom.DOMException,
1020
xml.parsers.expat.ExpatError) as error:
1021
logger.error("Failed to override Introspection method",
1026
def datetime_to_dbus (dt, variant_level=0):
1027
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1029
return dbus.String("", variant_level = variant_level)
1030
return dbus.String(dt.isoformat(),
1031
variant_level=variant_level)
1034
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1035
"""A class decorator; applied to a subclass of
1036
dbus.service.Object, it will add alternate D-Bus attributes with
1037
interface names according to the "alt_interface_names" mapping.
1040
@alternate_dbus_names({"org.example.Interface":
1041
"net.example.AlternateInterface"})
1042
class SampleDBusObject(dbus.service.Object):
1043
@dbus.service.method("org.example.Interface")
1044
def SampleDBusMethod():
1047
The above "SampleDBusMethod" on "SampleDBusObject" will be
1048
reachable via two interfaces: "org.example.Interface" and
1049
"net.example.AlternateInterface", the latter of which will have
1050
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1051
"true", unless "deprecate" is passed with a False value.
1053
This works for methods and signals, and also for D-Bus properties
1054
(from DBusObjectWithProperties) and interfaces (from the
1055
dbus_interface_annotations decorator).
1058
for orig_interface_name, alt_interface_name in (
1059
alt_interface_names.iteritems()):
1061
interface_names = set()
1062
# Go though all attributes of the class
1063
for attrname, attribute in inspect.getmembers(cls):
1064
# Ignore non-D-Bus attributes, and D-Bus attributes
1065
# with the wrong interface name
1066
if (not hasattr(attribute, "_dbus_interface")
1067
or not attribute._dbus_interface
1068
.startswith(orig_interface_name)):
1070
# Create an alternate D-Bus interface name based on
1072
alt_interface = (attribute._dbus_interface
1073
.replace(orig_interface_name,
1074
alt_interface_name))
1075
interface_names.add(alt_interface)
1076
# Is this a D-Bus signal?
1077
if getattr(attribute, "_dbus_is_signal", False):
1078
# Extract the original non-method function by
1080
nonmethod_func = (dict(
1081
zip(attribute.func_code.co_freevars,
1082
attribute.__closure__))["func"]
1084
# Create a new, but exactly alike, function
1085
# object, and decorate it to be a new D-Bus signal
1086
# with the alternate D-Bus interface name
1087
new_function = (dbus.service.signal
1089
attribute._dbus_signature)
1090
(types.FunctionType(
1091
nonmethod_func.func_code,
1092
nonmethod_func.func_globals,
1093
nonmethod_func.func_name,
1094
nonmethod_func.func_defaults,
1095
nonmethod_func.func_closure)))
1096
# Copy annotations, if any
1098
new_function._dbus_annotations = (
1099
dict(attribute._dbus_annotations))
1100
except AttributeError:
1102
# Define a creator of a function to call both the
1103
# original and alternate functions, so both the
1104
# original and alternate signals gets sent when
1105
# the function is called
1106
def fixscope(func1, func2):
1107
"""This function is a scope container to pass
1108
func1 and func2 to the "call_both" function
1109
outside of its arguments"""
1110
def call_both(*args, **kwargs):
1111
"""This function will emit two D-Bus
1112
signals by calling func1 and func2"""
1113
func1(*args, **kwargs)
1114
func2(*args, **kwargs)
1116
# Create the "call_both" function and add it to
1118
attr[attrname] = fixscope(attribute, new_function)
1119
# Is this a D-Bus method?
1120
elif getattr(attribute, "_dbus_is_method", False):
1121
# Create a new, but exactly alike, function
1122
# object. Decorate it to be a new D-Bus method
1123
# with the alternate D-Bus interface name. Add it
1125
attr[attrname] = (dbus.service.method
1127
attribute._dbus_in_signature,
1128
attribute._dbus_out_signature)
1130
(attribute.func_code,
1131
attribute.func_globals,
1132
attribute.func_name,
1133
attribute.func_defaults,
1134
attribute.func_closure)))
1135
# Copy annotations, if any
1137
attr[attrname]._dbus_annotations = (
1138
dict(attribute._dbus_annotations))
1139
except AttributeError:
1141
# Is this a D-Bus property?
1142
elif getattr(attribute, "_dbus_is_property", False):
1143
# Create a new, but exactly alike, function
1144
# object, and decorate it to be a new D-Bus
1145
# property with the alternate D-Bus interface
1146
# name. Add it to the class.
1147
attr[attrname] = (dbus_service_property
1149
attribute._dbus_signature,
1150
attribute._dbus_access,
1152
._dbus_get_args_options
1155
(attribute.func_code,
1156
attribute.func_globals,
1157
attribute.func_name,
1158
attribute.func_defaults,
1159
attribute.func_closure)))
1160
# Copy annotations, if any
1162
attr[attrname]._dbus_annotations = (
1163
dict(attribute._dbus_annotations))
1164
except AttributeError:
1166
# Is this a D-Bus interface?
1167
elif getattr(attribute, "_dbus_is_interface", False):
1168
# Create a new, but exactly alike, function
1169
# object. Decorate it to be a new D-Bus interface
1170
# with the alternate D-Bus interface name. Add it
1172
attr[attrname] = (dbus_interface_annotations
1175
(attribute.func_code,
1176
attribute.func_globals,
1177
attribute.func_name,
1178
attribute.func_defaults,
1179
attribute.func_closure)))
1181
# Deprecate all alternate interfaces
1182
iname="_AlternateDBusNames_interface_annotation{0}"
1183
for interface_name in interface_names:
1184
@dbus_interface_annotations(interface_name)
1186
return { "org.freedesktop.DBus.Deprecated":
1188
# Find an unused name
1189
for aname in (iname.format(i)
1190
for i in itertools.count()):
1191
if aname not in attr:
1195
# Replace the class with a new subclass of it with
1196
# methods, signals, etc. as created above.
1197
cls = type(b"{0}Alternate".format(cls.__name__),
1203
@alternate_dbus_interfaces({"se.recompile.Mandos":
1204
"se.bsnet.fukt.Mandos"})
1205
class ClientDBus(Client, DBusObjectWithProperties):
1206
"""A Client class using D-Bus
1209
dbus_object_path: dbus.ObjectPath
1210
bus: dbus.SystemBus()
1213
runtime_expansions = (Client.runtime_expansions
1214
+ ("dbus_object_path",))
1216
# dbus.service.Object doesn't use super(), so we can't either.
1218
def __init__(self, bus = None, *args, **kwargs):
1220
Client.__init__(self, *args, **kwargs)
1221
# Only now, when this client is initialized, can it show up on
1223
client_object_name = unicode(self.name).translate(
1224
{ord("."): ord("_"),
1225
ord("-"): ord("_")})
1226
self.dbus_object_path = (dbus.ObjectPath
1227
("/clients/" + client_object_name))
1228
DBusObjectWithProperties.__init__(self, self.bus,
1229
self.dbus_object_path)
1231
def notifychangeproperty(transform_func,
1232
dbus_name, type_func=lambda x: x,
1234
""" Modify a variable so that it's a property which announces
1235
its changes to DBus.
1237
transform_fun: Function that takes a value and a variant_level
1238
and transforms it to a D-Bus type.
1239
dbus_name: D-Bus name of the variable
1240
type_func: Function that transform the value before sending it
1241
to the D-Bus. Default: no transform
1242
variant_level: D-Bus variant level. Default: 1
1244
attrname = "_{0}".format(dbus_name)
1245
def setter(self, value):
1246
if hasattr(self, "dbus_object_path"):
1247
if (not hasattr(self, attrname) or
1248
type_func(getattr(self, attrname, None))
1249
!= type_func(value)):
1250
dbus_value = transform_func(type_func(value),
1253
self.PropertyChanged(dbus.String(dbus_name),
1255
setattr(self, attrname, value)
1257
return property(lambda self: getattr(self, attrname), setter)
1259
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1260
approvals_pending = notifychangeproperty(dbus.Boolean,
1263
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1264
last_enabled = notifychangeproperty(datetime_to_dbus,
1266
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1267
type_func = lambda checker:
1268
checker is not None)
1269
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1271
last_checker_status = notifychangeproperty(dbus.Int16,
1272
"LastCheckerStatus")
1273
last_approval_request = notifychangeproperty(
1274
datetime_to_dbus, "LastApprovalRequest")
1275
approved_by_default = notifychangeproperty(dbus.Boolean,
1276
"ApprovedByDefault")
1277
approval_delay = notifychangeproperty(dbus.UInt64,
1280
timedelta_to_milliseconds)
1281
approval_duration = notifychangeproperty(
1282
dbus.UInt64, "ApprovalDuration",
1283
type_func = timedelta_to_milliseconds)
1284
host = notifychangeproperty(dbus.String, "Host")
1285
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1287
timedelta_to_milliseconds)
1288
extended_timeout = notifychangeproperty(
1289
dbus.UInt64, "ExtendedTimeout",
1290
type_func = timedelta_to_milliseconds)
1291
interval = notifychangeproperty(dbus.UInt64,
1294
timedelta_to_milliseconds)
1295
checker_command = notifychangeproperty(dbus.String, "Checker")
1297
del notifychangeproperty
1299
def __del__(self, *args, **kwargs):
1301
self.remove_from_connection()
1304
if hasattr(DBusObjectWithProperties, "__del__"):
1305
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1306
Client.__del__(self, *args, **kwargs)
1308
def checker_callback(self, pid, condition, command,
1310
self.checker_callback_tag = None
1312
if os.WIFEXITED(condition):
1313
exitstatus = os.WEXITSTATUS(condition)
1315
self.CheckerCompleted(dbus.Int16(exitstatus),
1316
dbus.Int64(condition),
1317
dbus.String(command))
1320
self.CheckerCompleted(dbus.Int16(-1),
1321
dbus.Int64(condition),
1322
dbus.String(command))
1324
return Client.checker_callback(self, pid, condition, command,
1327
def start_checker(self, *args, **kwargs):
1328
old_checker = self.checker
1329
if self.checker is not None:
1330
old_checker_pid = self.checker.pid
1332
old_checker_pid = None
1333
r = Client.start_checker(self, *args, **kwargs)
1334
# Only if new checker process was started
1335
if (self.checker is not None
1336
and old_checker_pid != self.checker.pid):
1338
self.CheckerStarted(self.current_checker_command)
1341
def _reset_approved(self):
1342
self.approved = None
1345
def approve(self, value=True):
1346
self.approved = value
1347
gobject.timeout_add(timedelta_to_milliseconds
1348
(self.approval_duration),
1349
self._reset_approved)
1350
self.send_changedstate()
1352
## D-Bus methods, signals & properties
1353
_interface = "se.recompile.Mandos.Client"
1357
@dbus_interface_annotations(_interface)
1359
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1364
# CheckerCompleted - signal
1365
@dbus.service.signal(_interface, signature="nxs")
1366
def CheckerCompleted(self, exitcode, waitstatus, command):
1370
# CheckerStarted - signal
1371
@dbus.service.signal(_interface, signature="s")
1372
def CheckerStarted(self, command):
1376
# PropertyChanged - signal
1377
@dbus.service.signal(_interface, signature="sv")
1378
def PropertyChanged(self, property, value):
1382
# GotSecret - signal
1383
@dbus.service.signal(_interface)
1384
def GotSecret(self):
1386
Is sent after a successful transfer of secret from the Mandos
1387
server to mandos-client
1392
@dbus.service.signal(_interface, signature="s")
1393
def Rejected(self, reason):
1397
# NeedApproval - signal
1398
@dbus.service.signal(_interface, signature="tb")
1399
def NeedApproval(self, timeout, default):
1401
return self.need_approval()
1406
@dbus.service.method(_interface, in_signature="b")
1407
def Approve(self, value):
1410
# CheckedOK - method
1411
@dbus.service.method(_interface)
1412
def CheckedOK(self):
1416
@dbus.service.method(_interface)
1421
# StartChecker - method
1422
@dbus.service.method(_interface)
1423
def StartChecker(self):
1425
self.start_checker()
1428
@dbus.service.method(_interface)
1433
# StopChecker - method
1434
@dbus.service.method(_interface)
1435
def StopChecker(self):
1440
# ApprovalPending - property
1441
@dbus_service_property(_interface, signature="b", access="read")
1442
def ApprovalPending_dbus_property(self):
1443
return dbus.Boolean(bool(self.approvals_pending))
1445
# ApprovedByDefault - property
1446
@dbus_service_property(_interface, signature="b",
1448
def ApprovedByDefault_dbus_property(self, value=None):
1449
if value is None: # get
1450
return dbus.Boolean(self.approved_by_default)
1451
self.approved_by_default = bool(value)
1453
# ApprovalDelay - property
1454
@dbus_service_property(_interface, signature="t",
1456
def ApprovalDelay_dbus_property(self, value=None):
1457
if value is None: # get
1458
return dbus.UInt64(self.approval_delay_milliseconds())
1459
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1461
# ApprovalDuration - property
1462
@dbus_service_property(_interface, signature="t",
1464
def ApprovalDuration_dbus_property(self, value=None):
1465
if value is None: # get
1466
return dbus.UInt64(timedelta_to_milliseconds(
1467
self.approval_duration))
1468
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1471
@dbus_service_property(_interface, signature="s", access="read")
1472
def Name_dbus_property(self):
1473
return dbus.String(self.name)
1475
# Fingerprint - property
1476
@dbus_service_property(_interface, signature="s", access="read")
1477
def Fingerprint_dbus_property(self):
1478
return dbus.String(self.fingerprint)
1481
@dbus_service_property(_interface, signature="s",
1483
def Host_dbus_property(self, value=None):
1484
if value is None: # get
1485
return dbus.String(self.host)
1486
self.host = unicode(value)
1488
# Created - property
1489
@dbus_service_property(_interface, signature="s", access="read")
1490
def Created_dbus_property(self):
1491
return datetime_to_dbus(self.created)
1493
# LastEnabled - property
1494
@dbus_service_property(_interface, signature="s", access="read")
1495
def LastEnabled_dbus_property(self):
1496
return datetime_to_dbus(self.last_enabled)
1498
# Enabled - property
1499
@dbus_service_property(_interface, signature="b",
1501
def Enabled_dbus_property(self, value=None):
1502
if value is None: # get
1503
return dbus.Boolean(self.enabled)
1509
# LastCheckedOK - property
1510
@dbus_service_property(_interface, signature="s",
1512
def LastCheckedOK_dbus_property(self, value=None):
1513
if value is not None:
1516
return datetime_to_dbus(self.last_checked_ok)
1518
# LastCheckerStatus - property
1519
@dbus_service_property(_interface, signature="n",
1521
def LastCheckerStatus_dbus_property(self):
1522
return dbus.Int16(self.last_checker_status)
1524
# Expires - property
1525
@dbus_service_property(_interface, signature="s", access="read")
1526
def Expires_dbus_property(self):
1527
return datetime_to_dbus(self.expires)
1529
# LastApprovalRequest - property
1530
@dbus_service_property(_interface, signature="s", access="read")
1531
def LastApprovalRequest_dbus_property(self):
1532
return datetime_to_dbus(self.last_approval_request)
1534
# Timeout - property
1535
@dbus_service_property(_interface, signature="t",
1537
def Timeout_dbus_property(self, value=None):
1538
if value is None: # get
1539
return dbus.UInt64(self.timeout_milliseconds())
1540
old_timeout = self.timeout
1541
self.timeout = datetime.timedelta(0, 0, 0, value)
1542
# Reschedule disabling
1544
now = datetime.datetime.utcnow()
1545
self.expires += self.timeout - old_timeout
1546
if self.expires <= now:
1547
# The timeout has passed
1550
if (getattr(self, "disable_initiator_tag", None)
1553
gobject.source_remove(self.disable_initiator_tag)
1554
self.disable_initiator_tag = (
1555
gobject.timeout_add(
1556
timedelta_to_milliseconds(self.expires - now),
1559
# ExtendedTimeout - property
1560
@dbus_service_property(_interface, signature="t",
1562
def ExtendedTimeout_dbus_property(self, value=None):
1563
if value is None: # get
1564
return dbus.UInt64(self.extended_timeout_milliseconds())
1565
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1567
# Interval - property
1568
@dbus_service_property(_interface, signature="t",
1570
def Interval_dbus_property(self, value=None):
1571
if value is None: # get
1572
return dbus.UInt64(self.interval_milliseconds())
1573
self.interval = datetime.timedelta(0, 0, 0, value)
1574
if getattr(self, "checker_initiator_tag", None) is None:
1577
# Reschedule checker run
1578
gobject.source_remove(self.checker_initiator_tag)
1579
self.checker_initiator_tag = (gobject.timeout_add
1580
(value, self.start_checker))
1581
self.start_checker() # Start one now, too
1583
# Checker - property
1584
@dbus_service_property(_interface, signature="s",
1586
def Checker_dbus_property(self, value=None):
1587
if value is None: # get
1588
return dbus.String(self.checker_command)
1589
self.checker_command = unicode(value)
1591
# CheckerRunning - property
1592
@dbus_service_property(_interface, signature="b",
1594
def CheckerRunning_dbus_property(self, value=None):
1595
if value is None: # get
1596
return dbus.Boolean(self.checker is not None)
1598
self.start_checker()
1602
# ObjectPath - property
1603
@dbus_service_property(_interface, signature="o", access="read")
1604
def ObjectPath_dbus_property(self):
1605
return self.dbus_object_path # is already a dbus.ObjectPath
1608
@dbus_service_property(_interface, signature="ay",
1609
access="write", byte_arrays=True)
1610
def Secret_dbus_property(self, value):
1611
self.secret = str(value)
1616
class ProxyClient(object):
1617
def __init__(self, child_pipe, fpr, address):
1618
self._pipe = child_pipe
1619
self._pipe.send(('init', fpr, address))
1620
if not self._pipe.recv():
1623
def __getattribute__(self, name):
1625
return super(ProxyClient, self).__getattribute__(name)
1626
self._pipe.send(('getattr', name))
1627
data = self._pipe.recv()
1628
if data[0] == 'data':
1630
if data[0] == 'function':
1631
def func(*args, **kwargs):
1632
self._pipe.send(('funcall', name, args, kwargs))
1633
return self._pipe.recv()[1]
1636
def __setattr__(self, name, value):
1638
return super(ProxyClient, self).__setattr__(name, value)
1639
self._pipe.send(('setattr', name, value))
1642
class ClientHandler(socketserver.BaseRequestHandler, object):
1643
"""A class to handle client connections.
1645
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.
1646
442
Note: This will run in its own forked process."""
1648
444
def handle(self):
1649
with contextlib.closing(self.server.child_pipe) as child_pipe:
1650
logger.info("TCP connection from: %s",
1651
unicode(self.client_address))
1652
logger.debug("Pipe FD: %d",
1653
self.server.child_pipe.fileno())
1655
session = (gnutls.connection
1656
.ClientSession(self.request,
1658
.X509Credentials()))
1660
# Note: gnutls.connection.X509Credentials is really a
1661
# generic GnuTLS certificate credentials object so long as
1662
# no X.509 keys are added to it. Therefore, we can use it
1663
# here despite using OpenPGP certificates.
1665
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1666
# "+AES-256-CBC", "+SHA1",
1667
# "+COMP-NULL", "+CTYPE-OPENPGP",
1669
# Use a fallback default, since this MUST be set.
1670
priority = self.server.gnutls_priority
1671
if priority is None:
1673
(gnutls.library.functions
1674
.gnutls_priority_set_direct(session._c_object,
1677
# Start communication using the Mandos protocol
1678
# Get protocol number
1679
line = self.request.makefile().readline()
1680
logger.debug("Protocol version: %r", line)
1682
if int(line.strip().split()[0]) > 1:
1684
except (ValueError, IndexError, RuntimeError) as error:
1685
logger.error("Unknown protocol version: %s", error)
1688
# Start GnuTLS connection
1691
except gnutls.errors.GNUTLSError as error:
1692
logger.warning("Handshake failed: %s", error)
1693
# Do not run session.bye() here: the session is not
1694
# established. Just abandon the request.
1696
logger.debug("Handshake succeeded")
1698
approval_required = False
1701
fpr = self.fingerprint(self.peer_certificate
1704
gnutls.errors.GNUTLSError) as error:
1705
logger.warning("Bad certificate: %s", error)
1707
logger.debug("Fingerprint: %s", fpr)
1710
client = ProxyClient(child_pipe, fpr,
1711
self.client_address)
1715
if client.approval_delay:
1716
delay = client.approval_delay
1717
client.approvals_pending += 1
1718
approval_required = True
1721
if not client.enabled:
1722
logger.info("Client %s is disabled",
1724
if self.server.use_dbus:
1726
client.Rejected("Disabled")
1729
if client.approved or not client.approval_delay:
1730
#We are approved or approval is disabled
1732
elif client.approved is None:
1733
logger.info("Client %s needs approval",
1735
if self.server.use_dbus:
1737
client.NeedApproval(
1738
client.approval_delay_milliseconds(),
1739
client.approved_by_default)
1741
logger.warning("Client %s was not approved",
1743
if self.server.use_dbus:
1745
client.Rejected("Denied")
1748
#wait until timeout or approved
1749
time = datetime.datetime.now()
1750
client.changedstate.acquire()
1751
client.changedstate.wait(
1752
float(timedelta_to_milliseconds(delay)
1754
client.changedstate.release()
1755
time2 = datetime.datetime.now()
1756
if (time2 - time) >= delay:
1757
if not client.approved_by_default:
1758
logger.warning("Client %s timed out while"
1759
" waiting for approval",
1761
if self.server.use_dbus:
1763
client.Rejected("Approval timed out")
1768
delay -= time2 - time
1771
while sent_size < len(client.secret):
1773
sent = session.send(client.secret[sent_size:])
1774
except gnutls.errors.GNUTLSError as error:
1775
logger.warning("gnutls send failed",
1778
logger.debug("Sent: %d, remaining: %d",
1779
sent, len(client.secret)
1780
- (sent_size + sent))
1783
logger.info("Sending secret to %s", client.name)
1784
# bump the timeout using extended_timeout
1785
client.bump_timeout(client.extended_timeout)
1786
if self.server.use_dbus:
1791
if approval_required:
1792
client.approvals_pending -= 1
1795
except gnutls.errors.GNUTLSError as error:
1796
logger.warning("GnuTLS bye failed",
1800
def peer_certificate(session):
1801
"Return the peer's OpenPGP certificate as a bytestring"
1802
# If not an OpenPGP certificate...
1803
if (gnutls.library.functions
1804
.gnutls_certificate_type_get(session._c_object)
1805
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1806
# ...do the normal thing
1807
return session.peer_certificate
1808
list_size = ctypes.c_uint(1)
1809
cert_list = (gnutls.library.functions
1810
.gnutls_certificate_get_peers
1811
(session._c_object, ctypes.byref(list_size)))
1812
if not bool(cert_list) and list_size.value != 0:
1813
raise gnutls.errors.GNUTLSError("error getting peer"
1815
if list_size.value == 0:
1818
return ctypes.string_at(cert.data, cert.size)
1821
def fingerprint(openpgp):
1822
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1823
# New GnuTLS "datum" with the OpenPGP public key
1824
datum = (gnutls.library.types
1825
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1828
ctypes.c_uint(len(openpgp))))
1829
# New empty GnuTLS certificate
1830
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1831
(gnutls.library.functions
1832
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1833
# Import the OpenPGP public key into the certificate
1834
(gnutls.library.functions
1835
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1836
gnutls.library.constants
1837
.GNUTLS_OPENPGP_FMT_RAW))
1838
# Verify the self signature in the key
1839
crtverify = ctypes.c_uint()
1840
(gnutls.library.functions
1841
.gnutls_openpgp_crt_verify_self(crt, 0,
1842
ctypes.byref(crtverify)))
1843
if crtverify.value != 0:
1844
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1845
raise (gnutls.errors.CertificateSecurityError
1847
# New buffer for the fingerprint
1848
buf = ctypes.create_string_buffer(20)
1849
buf_len = ctypes.c_size_t()
1850
# Get the fingerprint from the certificate into the buffer
1851
(gnutls.library.functions
1852
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1853
ctypes.byref(buf_len)))
1854
# Deinit the certificate
1855
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1856
# Convert the buffer to a Python bytestring
1857
fpr = ctypes.string_at(buf, buf_len.value)
1858
# Convert the bytestring to hexadecimal notation
1859
hex_fpr = binascii.hexlify(fpr).upper()
1863
class MultiprocessingMixIn(object):
1864
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1865
def sub_process_main(self, request, address):
1867
self.finish_request(request, address)
1869
self.handle_error(request, address)
1870
self.close_request(request)
1872
def process_request(self, request, address):
1873
"""Start a new process to process the request."""
1874
proc = multiprocessing.Process(target = self.sub_process_main,
1875
args = (request, address))
1880
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1881
""" adds a pipe to the MixIn """
1882
def process_request(self, request, client_address):
1883
"""Overrides and wraps the original process_request().
1885
This function creates a new pipe in self.pipe
1887
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1889
proc = MultiprocessingMixIn.process_request(self, request,
1891
self.child_pipe.close()
1892
self.add_pipe(parent_pipe, proc)
1894
def add_pipe(self, parent_pipe, proc):
1895
"""Dummy function; override as necessary"""
1896
raise NotImplementedError
1899
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1900
socketserver.TCPServer, object):
1901
"""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
1904
521
enabled: Boolean; whether this server is activated yet
1905
interface: None or a network interface name (string)
1906
use_ipv6: Boolean; to use IPv6 or not
1908
def __init__(self, server_address, RequestHandlerClass,
1909
interface=None, use_ipv6=True):
1910
self.interface = interface
1912
self.address_family = socket.AF_INET6
1913
socketserver.TCPServer.__init__(self, server_address,
1914
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)
1915
533
def server_bind(self):
1916
534
"""This overrides the normal server_bind() function
1917
535
to bind to an interface if one was specified, and also NOT to
1918
536
bind to an address or port if they were not specified."""
1919
if self.interface is not None:
1920
if SO_BINDTODEVICE is None:
1921
logger.error("SO_BINDTODEVICE does not exist;"
1922
" cannot bind to interface %s",
1926
self.socket.setsockopt(socket.SOL_SOCKET,
1930
except socket.error as error:
1931
if error.errno == errno.EPERM:
1932
logger.error("No permission to"
1933
" bind to interface %s",
1935
elif error.errno == errno.ENOPROTOOPT:
1936
logger.error("SO_BINDTODEVICE not available;"
1937
" cannot bind to interface %s",
1939
elif error.errno == errno.ENODEV:
1940
logger.error("Interface %s does not"
1941
" exist, cannot bind",
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"])
1945
551
# Only bind(2) the socket if we really need to.
1946
552
if self.server_address[0] or self.server_address[1]:
1947
553
if not self.server_address[0]:
1948
if self.address_family == socket.AF_INET6:
1949
any_address = "::" # in6addr_any
1951
any_address = socket.INADDR_ANY
1952
self.server_address = (any_address,
555
self.server_address = (in6addr_any,
1953
556
self.server_address[1])
1954
557
elif not self.server_address[1]:
1955
558
self.server_address = (self.server_address[0],
1957
# if self.interface:
560
# if self.settings["interface"]:
1958
561
# self.server_address = (self.server_address[0],
1961
564
# if_nametoindex
1963
return socketserver.TCPServer.server_bind(self)
1966
class MandosServer(IPv6_TCPServer):
1970
clients: set of Client objects
1971
gnutls_priority GnuTLS priority string
1972
use_dbus: Boolean; to emit D-Bus signals or not
1974
Assumes a gobject.MainLoop event loop.
1976
def __init__(self, server_address, RequestHandlerClass,
1977
interface=None, use_ipv6=True, clients=None,
1978
gnutls_priority=None, use_dbus=True):
1979
self.enabled = False
1980
self.clients = clients
1981
if self.clients is None:
1983
self.use_dbus = use_dbus
1984
self.gnutls_priority = gnutls_priority
1985
IPv6_TCPServer.__init__(self, server_address,
1986
RequestHandlerClass,
1987
interface = interface,
1988
use_ipv6 = use_ipv6)
567
return super(type(self), self).server_bind()
1989
568
def server_activate(self):
1990
569
if self.enabled:
1991
return socketserver.TCPServer.server_activate(self)
570
return super(type(self), self).server_activate()
1993
571
def enable(self):
1994
572
self.enabled = True
1996
def add_pipe(self, parent_pipe, proc):
1997
# Call "handle_ipc" for both data and EOF events
1998
gobject.io_add_watch(parent_pipe.fileno(),
1999
gobject.IO_IN | gobject.IO_HUP,
2000
functools.partial(self.handle_ipc,
2005
def handle_ipc(self, source, condition, parent_pipe=None,
2006
proc = None, client_object=None):
2007
# error, or the other end of multiprocessing.Pipe has closed
2008
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2009
# Wait for other process to exit
2013
# Read a request from the child
2014
request = parent_pipe.recv()
2015
command = request[0]
2017
if command == 'init':
2019
address = request[2]
2021
for c in self.clients.itervalues():
2022
if c.fingerprint == fpr:
2026
logger.info("Client not found for fingerprint: %s, ad"
2027
"dress: %s", fpr, address)
2030
mandos_dbus_service.ClientNotFound(fpr,
2032
parent_pipe.send(False)
2035
gobject.io_add_watch(parent_pipe.fileno(),
2036
gobject.IO_IN | gobject.IO_HUP,
2037
functools.partial(self.handle_ipc,
2043
parent_pipe.send(True)
2044
# remove the old hook in favor of the new above hook on
2047
if command == 'funcall':
2048
funcname = request[1]
2052
parent_pipe.send(('data', getattr(client_object,
2056
if command == 'getattr':
2057
attrname = request[1]
2058
if callable(client_object.__getattribute__(attrname)):
2059
parent_pipe.send(('function',))
2061
parent_pipe.send(('data', client_object
2062
.__getattribute__(attrname)))
2064
if command == 'setattr':
2065
attrname = request[1]
2067
setattr(client_object, attrname, value)
2072
575
def string_to_delta(interval):
2073
576
"""Parse a string and return a datetime.timedelta
2075
578
>>> string_to_delta('7d')
2076
579
datetime.timedelta(7)
2077
580
>>> string_to_delta('60s')
2191
722
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2192
723
"servicename": "Mandos",
2197
"statedir": "/var/lib/mandos"
2200
726
# Parse config file for server-global settings
2201
server_config = configparser.SafeConfigParser(server_defaults)
727
server_config = ConfigParser.SafeConfigParser(server_defaults)
2202
728
del server_defaults
2203
server_config.read(os.path.join(options.configdir,
729
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2205
730
# Convert the SafeConfigParser object to a dict
2206
731
server_settings = server_config.defaults()
2207
# Use the appropriate methods on the non-string config options
2208
for option in ("debug", "use_dbus", "use_ipv6"):
2209
server_settings[option] = server_config.getboolean("DEFAULT",
2211
if server_settings["port"]:
2212
server_settings["port"] = server_config.getint("DEFAULT",
732
# Use getboolean on the boolean config option
733
server_settings["debug"] = server_config.getboolean\
2214
735
del server_config
2216
737
# Override the settings from the config file with command line
2217
738
# options, if set.
2218
739
for option in ("interface", "address", "port", "debug",
2219
"priority", "servicename", "configdir",
2220
"use_dbus", "use_ipv6", "debuglevel", "restore",
740
"priority", "servicename", "configdir"):
2222
741
value = getattr(options, option)
2223
742
if value is not None:
2224
743
server_settings[option] = value
2226
# Force all strings to be unicode
2227
for option in server_settings.keys():
2228
if type(server_settings[option]) is str:
2229
server_settings[option] = unicode(server_settings[option])
2230
745
# Now we have our good server settings in "server_settings"
2232
##################################################################
2235
747
debug = server_settings["debug"]
2236
debuglevel = server_settings["debuglevel"]
2237
use_dbus = server_settings["use_dbus"]
2238
use_ipv6 = server_settings["use_ipv6"]
2239
stored_state_path = os.path.join(server_settings["statedir"],
2243
initlogger(debug, logging.DEBUG)
2248
level = getattr(logging, debuglevel.upper())
2249
initlogger(debug, level)
750
syslogger.setLevel(logging.WARNING)
751
console.setLevel(logging.WARNING)
2251
753
if server_settings["servicename"] != "Mandos":
2252
syslogger.setFormatter(logging.Formatter
2253
('Mandos ({0}) [%(process)d]:'
2254
' %(levelname)s: %(message)s'
2255
.format(server_settings
754
syslogger.setFormatter(logging.Formatter\
755
('Mandos (%s): %%(levelname)s:'
757
% server_settings["servicename"]))
2258
759
# Parse config file with clients
2259
client_config = configparser.SafeConfigParser(Client
760
client_defaults = { "timeout": "1h",
762
"checker": "fping -q -- %(host)s",
765
client_config = ConfigParser.SafeConfigParser(client_defaults)
2261
766
client_config.read(os.path.join(server_settings["configdir"],
2262
767
"clients.conf"))
2264
global mandos_dbus_service
2265
mandos_dbus_service = None
2267
tcp_server = MandosServer((server_settings["address"],
2268
server_settings["port"]),
2270
interface=(server_settings["interface"]
2274
server_settings["priority"],
2277
pidfilename = "/var/run/mandos.pid"
2279
pidfile = open(pidfilename, "w")
2280
except IOError as e:
2281
logger.error("Could not open file %r", pidfilename,
2284
for name in ("_mandos", "mandos", "nobody"):
2286
uid = pwd.getpwnam(name).pw_uid
2287
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
2297
except OSError as error:
800
except OSError, error:
2298
801
if error[0] != errno.EPERM:
2302
# Enable all possible GnuTLS debugging
2304
# "Use a log level over 10 to enable all debugging options."
2306
gnutls.library.functions.gnutls_global_set_log_level(11)
2308
@gnutls.library.types.gnutls_log_func
2309
def debug_gnutls(level, string):
2310
logger.debug("GnuTLS: %s", string[:-1])
2312
(gnutls.library.functions
2313
.gnutls_global_set_log_function(debug_gnutls))
2315
# Redirect stdin so all checkers get /dev/null
2316
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2317
os.dup2(null, sys.stdin.fileno())
2321
# Need to fork before connecting to D-Bus
2323
# Close all input and output, do double fork, etc.
2326
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"])
2328
811
global main_loop
2329
814
# From the Avahi example code
2330
DBusGMainLoop(set_as_default=True)
815
DBusGMainLoop(set_as_default=True )
2331
816
main_loop = gobject.MainLoop()
2332
817
bus = dbus.SystemBus()
818
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
819
avahi.DBUS_PATH_SERVER),
820
avahi.DBUS_INTERFACE_SERVER)
2333
821
# End of Avahi example code
2336
bus_name = dbus.service.BusName("se.recompile.Mandos",
2337
bus, do_not_queue=True)
2338
old_bus_name = (dbus.service.BusName
2339
("se.bsnet.fukt.Mandos", bus,
2341
except dbus.exceptions.NameExistsException as e:
2342
logger.error("Disabling D-Bus:", exc_info=e)
2344
server_settings["use_dbus"] = False
2345
tcp_server.use_dbus = False
2346
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2347
service = AvahiServiceToSyslog(name =
2348
server_settings["servicename"],
2349
servicetype = "_mandos._tcp",
2350
protocol = protocol, bus = bus)
2351
if server_settings["interface"]:
2352
service.interface = (if_nametoindex
2353
(str(server_settings["interface"])))
2355
global multiprocessing_manager
2356
multiprocessing_manager = multiprocessing.Manager()
2358
client_class = Client
2360
client_class = functools.partial(ClientDBus, bus = bus)
2362
client_settings = Client.config_parser(client_config)
2363
old_client_settings = {}
2366
# Get client data and settings from last running state.
2367
if server_settings["restore"]:
2369
with open(stored_state_path, "rb") as stored_state:
2370
clients_data, old_client_settings = (pickle.load
2372
os.remove(stored_state_path)
2373
except IOError as e:
2374
if e.errno == errno.ENOENT:
2375
logger.warning("Could not load persistent state: {0}"
2376
.format(os.strerror(e.errno)))
2378
logger.critical("Could not load persistent state:",
2381
except EOFError as e:
2382
logger.warning("Could not load persistent state: "
2383
"EOFError:", exc_info=e)
2385
with PGPEngine() as pgp:
2386
for client_name, client in clients_data.iteritems():
2387
# Decide which value to use after restoring saved state.
2388
# We have three different values: Old config file,
2389
# new config file, and saved state.
2390
# New config value takes precedence if it differs from old
2391
# config value, otherwise use saved state.
2392
for name, value in client_settings[client_name].items():
2394
# For each value in new config, check if it
2395
# differs from the old config value (Except for
2396
# the "secret" attribute)
2397
if (name != "secret" and
2398
value != old_client_settings[client_name]
2400
client[name] = value
2404
# Clients who has passed its expire date can still be
2405
# enabled if its last checker was successful. Clients
2406
# whose checker succeeded before we stored its state is
2407
# assumed to have successfully run all checkers during
2409
if client["enabled"]:
2410
if datetime.datetime.utcnow() >= client["expires"]:
2411
if not client["last_checked_ok"]:
2413
"disabling client {0} - Client never "
2414
"performed a successful checker"
2415
.format(client_name))
2416
client["enabled"] = False
2417
elif client["last_checker_status"] != 0:
2419
"disabling client {0} - Client "
2420
"last checker failed with error code {1}"
2421
.format(client_name,
2422
client["last_checker_status"]))
2423
client["enabled"] = False
2425
client["expires"] = (datetime.datetime
2427
+ client["timeout"])
2428
logger.debug("Last checker succeeded,"
2429
" keeping {0} enabled"
2430
.format(client_name))
2432
client["secret"] = (
2433
pgp.decrypt(client["encrypted_secret"],
2434
client_settings[client_name]
2437
# If decryption fails, we use secret from new settings
2438
logger.debug("Failed to decrypt {0} old secret"
2439
.format(client_name))
2440
client["secret"] = (
2441
client_settings[client_name]["secret"])
2443
# Add/remove clients based on new changes made to config
2444
for client_name in (set(old_client_settings)
2445
- set(client_settings)):
2446
del clients_data[client_name]
2447
for client_name in (set(client_settings)
2448
- set(old_client_settings)):
2449
clients_data[client_name] = client_settings[client_name]
2451
# Create all client objects
2452
for client_name, client in clients_data.iteritems():
2453
tcp_server.clients[client_name] = client_class(
2454
name = client_name, settings = client)
2456
if not tcp_server.clients:
2457
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)
2463
pidfile.write(str(pid) + "\n".encode("utf-8"))
2466
logger.error("Could not write to file %r with PID %d",
2469
# "pidfile" was never created
2472
880
signal.signal(signal.SIGINT, signal.SIG_IGN)
2474
881
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2475
882
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2478
@alternate_dbus_interfaces({"se.recompile.Mandos":
2479
"se.bsnet.fukt.Mandos"})
2480
class MandosDBusService(DBusObjectWithProperties):
2481
"""A D-Bus proxy object"""
2483
dbus.service.Object.__init__(self, bus, "/")
2484
_interface = "se.recompile.Mandos"
2486
@dbus_interface_annotations(_interface)
2488
return { "org.freedesktop.DBus.Property"
2489
".EmitsChangedSignal":
2492
@dbus.service.signal(_interface, signature="o")
2493
def ClientAdded(self, objpath):
2497
@dbus.service.signal(_interface, signature="ss")
2498
def ClientNotFound(self, fingerprint, address):
2502
@dbus.service.signal(_interface, signature="os")
2503
def ClientRemoved(self, objpath, name):
2507
@dbus.service.method(_interface, out_signature="ao")
2508
def GetAllClients(self):
2510
return dbus.Array(c.dbus_object_path
2512
tcp_server.clients.itervalues())
2514
@dbus.service.method(_interface,
2515
out_signature="a{oa{sv}}")
2516
def GetAllClientsWithProperties(self):
2518
return dbus.Dictionary(
2519
((c.dbus_object_path, c.GetAll(""))
2520
for c in tcp_server.clients.itervalues()),
2523
@dbus.service.method(_interface, in_signature="o")
2524
def RemoveClient(self, object_path):
2526
for c in tcp_server.clients.itervalues():
2527
if c.dbus_object_path == object_path:
2528
del tcp_server.clients[c.name]
2529
c.remove_from_connection()
2530
# Don't signal anything except ClientRemoved
2531
c.disable(quiet=True)
2533
self.ClientRemoved(object_path, c.name)
2535
raise KeyError(object_path)
2539
mandos_dbus_service = MandosDBusService()
2542
"Cleanup function; run on exit"
2545
multiprocessing.active_children()
2546
if not (tcp_server.clients or client_settings):
2549
# Store client before exiting. Secrets are encrypted with key
2550
# based on what config file has. If config file is
2551
# removed/edited, old secret will thus be unrecovable.
2553
with PGPEngine() as pgp:
2554
for client in tcp_server.clients.itervalues():
2555
key = client_settings[client.name]["secret"]
2556
client.encrypted_secret = pgp.encrypt(client.secret,
2560
# A list of attributes that can not be pickled
2562
exclude = set(("bus", "changedstate", "secret",
2564
for name, typ in (inspect.getmembers
2565
(dbus.service.Object)):
2568
client_dict["encrypted_secret"] = (client
2570
for attr in client.client_structure:
2571
if attr not in exclude:
2572
client_dict[attr] = getattr(client, attr)
2574
clients[client.name] = client_dict
2575
del client_settings[client.name]["secret"]
2578
with (tempfile.NamedTemporaryFile
2579
(mode='wb', suffix=".pickle", prefix='clients-',
2580
dir=os.path.dirname(stored_state_path),
2581
delete=False)) as stored_state:
2582
pickle.dump((clients, client_settings), stored_state)
2583
tempname=stored_state.name
2584
os.rename(tempname, stored_state_path)
2585
except (IOError, OSError) as e:
2591
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2592
logger.warning("Could not save persistent state: {0}"
2593
.format(os.strerror(e.errno)))
2595
logger.warning("Could not save persistent state:",
2599
# Delete all clients, and settings from config
2600
while tcp_server.clients:
2601
name, client = tcp_server.clients.popitem()
2603
client.remove_from_connection()
2604
# Don't signal anything except ClientRemoved
2605
client.disable(quiet=True)
2608
mandos_dbus_service.ClientRemoved(client
2611
client_settings.clear()
2613
atexit.register(cleanup)
2615
for client in tcp_server.clients.itervalues():
2618
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2619
# Need to initiate checking of clients
2621
client.init_checker()
884
for client in clients:
2623
887
tcp_server.enable()
2624
888
tcp_server.server_activate()
2626
890
# Find out what port we got
2627
891
service.port = tcp_server.socket.getsockname()[1]
2629
logger.info("Now listening on address %r, port %d,"
2630
" flowinfo %d, scope_id %d",
2631
*tcp_server.socket.getsockname())
2633
logger.info("Now listening on address %r, port %d",
2634
*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())
2636
895
#service.interface = tcp_server.socket.getsockname()[3]
2639
898
# From the Avahi example code
899
server.connect_to_signal("StateChanged", server_state_changed)
2642
except dbus.exceptions.DBusException as error:
2643
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)
2646
905
# End of Avahi example code
2648
907
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2649
908
lambda *args, **kwargs:
2650
(tcp_server.handle_request
2651
(*args[2:], **kwargs) or True))
909
tcp_server.handle_request\
910
(*args[2:], **kwargs) or True)
2653
logger.debug("Starting main loop")
912
logger.debug(u"Starting main loop")
913
main_loop_started = True
2655
except AvahiError as error:
2656
logger.critical("Avahi Error", exc_info=error)
915
except AvahiError, error:
916
logger.critical(u"AvahiError: %s" + unicode(error))
2659
918
except KeyboardInterrupt:
2661
print("", file=sys.stderr)
2662
logger.debug("Server received KeyboardInterrupt")
2663
logger.debug("Server exiting")
2664
# Must run before the D-Bus bus name gets deregistered
2667
922
if __name__ == '__main__':