241
104
max_renames: integer; maximum number of renames
242
105
rename_count: integer; counter so we only rename after collisions
243
106
a sensible number of times
244
group: D-Bus Entry Group
246
bus: dbus.SystemBus()
249
108
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):
109
type = None, port = None, TXT = None, domain = "",
110
host = "", max_renames = 32768):
253
111
self.interface = interface
255
self.type = servicetype
257
self.TXT = TXT if TXT is not None else []
258
119
self.domain = domain
260
121
self.rename_count = 0
261
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
122
def rename(self):
269
123
"""Derived from the Avahi example code"""
270
124
if self.rename_count >= self.max_renames:
271
logger.critical("No suitable Zeroconf service name found"
272
" after %i retries, exiting.",
125
logger.critical(u"No suitable service name found after %i"
126
u" retries, exiting.", rename_count)
274
127
raise AvahiServiceError("Too many renames")
275
self.name = unicode(self.server
276
.GetAlternativeServiceName(self.name))
277
logger.info("Changing Zeroconf service name to %r ...",
128
name = server.GetAlternativeServiceName(name)
129
logger.error(u"Changing name to %r ...", name)
130
syslogger.setFormatter(logging.Formatter\
131
('Mandos (%s): %%(levelname)s:'
132
' %%(message)s' % name))
282
except dbus.exceptions.DBusException as error:
283
logger.critical("D-Bus Exception", exc_info=error)
286
135
self.rename_count += 1
288
136
def remove(self):
289
137
"""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:
138
if group is not None:
297
141
"""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))
144
group = dbus.Interface\
145
(bus.get_object(avahi.DBUS_NAME,
146
server.EntryGroupNew()),
147
avahi.DBUS_INTERFACE_ENTRY_GROUP)
148
group.connect_to_signal('StateChanged',
149
entry_group_state_changed)
150
logger.debug(u"Adding service '%s' of type '%s' ...",
151
service.name, service.type)
153
self.interface, # interface
154
avahi.PROTO_INET6, # protocol
155
dbus.UInt32(0), # flags
156
self.name, self.type,
157
self.domain, self.host,
158
dbus.UInt16(self.port),
159
avahi.string_array_to_txt_array(self.TXT))
162
# From the Avahi example code:
163
group = None # our entry group
164
# End of Avahi example code
401
167
class Client(object):
402
168
"""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
170
name: string; from the config file, used in log messages
171
fingerprint: string (40 or 32 hexadecimal digits); used to
172
uniquely identify the client
173
secret: bytestring; sent verbatim (over TLS) to client
174
host: string; available for use by the checker command
175
created: datetime.datetime(); object creation, not client host
176
last_checked_ok: datetime.datetime() or None if not yet checked OK
177
timeout: datetime.timedelta(); How long from last_checked_ok
178
until this client is invalid
179
interval: datetime.timedelta(); How often to start a new checker
180
stop_hook: If set, called by stop() as stop_hook(self)
181
checker: subprocess.Popen(); a running checker process used
182
to see if the client lives.
183
'None' if no process is running.
184
checker_initiator_tag: a gobject event source tag, or None
185
stop_initiator_tag: - '' -
186
checker_callback_tag: - '' -
187
checker_command: string; External command which is run to check if
188
client lives. %() expansions are done at
414
189
runtime with vars(self) as dict, so that for
415
190
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
192
_timeout: Real variable for 'timeout'
193
_interval: Real variable for 'interval'
194
_timeout_milliseconds: Used when calling gobject.timeout_add()
195
_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):
197
def _set_timeout(self, timeout):
198
"Setter function for 'timeout' attribute"
199
self._timeout = timeout
200
self._timeout_milliseconds = ((self.timeout.days
201
* 24 * 60 * 60 * 1000)
202
+ (self.timeout.seconds * 1000)
203
+ (self.timeout.microseconds
205
timeout = property(lambda self: self._timeout,
208
def _set_interval(self, interval):
209
"Setter function for 'interval' attribute"
210
self._interval = interval
211
self._interval_milliseconds = ((self.interval.days
212
* 24 * 60 * 60 * 1000)
213
+ (self.interval.seconds
215
+ (self.interval.microseconds
217
interval = property(lambda self: self._interval,
220
def __init__(self, name = None, stop_hook=None, config={}):
221
"""Note: the 'checker' key in 'config' sets the
222
'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)
225
logger.debug(u"Creating client %r", self.name)
538
226
# Uppercase and remove spaces from fingerprint for later
539
227
# 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
229
self.fingerprint = config["fingerprint"].upper()\
231
logger.debug(u" Fingerprint: %s", self.fingerprint)
232
if "secret" in config:
233
self.secret = config["secret"].decode(u"base64")
234
elif "secfile" in config:
235
sf = open(config["secfile"])
236
self.secret = sf.read()
239
raise TypeError(u"No secret or secfile for client %s"
241
self.host = config.get("host", "")
242
self.created = datetime.datetime.now()
243
self.last_checked_ok = None
244
self.timeout = string_to_delta(config["timeout"])
245
self.interval = string_to_delta(config["interval"])
246
self.stop_hook = stop_hook
546
247
self.checker = None
547
248
self.checker_initiator_tag = None
548
self.disable_initiator_tag = None
249
self.stop_initiator_tag = None
549
250
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()
251
self.check_command = config["checker"]
574
253
"""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
254
# Schedule a new checker to be started an 'interval' from now,
609
255
# 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(),
256
self.checker_initiator_tag = gobject.timeout_add\
257
(self._interval_milliseconds,
621
259
# Also start a new checker *right now*.
622
260
self.start_checker()
624
def checker_callback(self, pid, condition, command):
261
# Schedule a stop() when 'timeout' has passed
262
self.stop_initiator_tag = gobject.timeout_add\
263
(self._timeout_milliseconds,
267
The possibility that a client might be restarted is left open,
268
but not currently used."""
269
# If this client doesn't have a secret, it is already stopped.
270
if hasattr(self, "secret") and self.secret:
271
logger.info(u"Stopping client %s", self.name)
275
if getattr(self, "stop_initiator_tag", False):
276
gobject.source_remove(self.stop_initiator_tag)
277
self.stop_initiator_tag = None
278
if getattr(self, "checker_initiator_tag", False):
279
gobject.source_remove(self.checker_initiator_tag)
280
self.checker_initiator_tag = None
284
# Do not run this again if called by a gobject.timeout_add
287
self.stop_hook = None
289
def checker_callback(self, pid, condition):
625
290
"""The checker has completed, so take appropriate actions."""
291
now = datetime.datetime.now()
626
292
self.checker_callback_tag = None
627
293
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?",
294
if os.WIFEXITED(condition) \
295
and (os.WEXITSTATUS(condition) == 0):
296
logger.info(u"Checker for %(name)s succeeded",
298
self.last_checked_ok = now
299
gobject.source_remove(self.stop_initiator_tag)
300
self.stop_initiator_tag = gobject.timeout_add\
301
(self._timeout_milliseconds,
303
elif not os.WIFEXITED(condition):
304
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()
307
logger.info(u"Checker for %(name)s failed",
664
309
def start_checker(self):
665
310
"""Start a new checker subprocess if one is not running.
667
311
If a checker already exists, leave it running and do
669
313
# 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
314
# did that, then if a checker (for some reason) started
315
# running slowly and taking more than 'interval' time, the
316
# client would inevitably timeout, since no checker would get
317
# a chance to run to completion. If we instead leave running
674
318
# 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
319
# than 'timeout' for the client to be declared invalid, which
320
# is as it should be.
692
321
if self.checker is None:
693
# Escape attributes for the shell
694
escaped_attrs = dict(
695
(attr, re.escape(unicode(getattr(self, attr))))
697
self.runtime_expansions)
699
command = self.checker_command % escaped_attrs
700
except TypeError as error:
701
logger.error('Could not format string "%s"',
702
self.checker_command, exc_info=error)
703
return True # Try again later
704
self.current_checker_command = command
706
logger.info("Starting checker %r for %s",
323
# In case check_command has exactly one % operator
324
command = self.check_command % self.host
326
# Escape attributes for the shell
327
escaped_attrs = dict((key, re.escape(str(val)))
329
vars(self).iteritems())
331
command = self.check_command % escaped_attrs
332
except TypeError, error:
333
logger.error(u'Could not format string "%s":'
334
u' %s', self.check_command, error)
335
return True # Try again later
337
logger.info(u"Starting checker %r for %s",
707
338
command, self.name)
708
# We don't need to redirect stdout and stderr, since
709
# in normal mode, that is already done by daemon(),
710
# and in debug mode we don't want to. (Stdin is
711
# always replaced by /dev/null.)
712
339
self.checker = subprocess.Popen(command,
714
341
shell=True, cwd="/")
715
except OSError as error:
716
logger.error("Failed to start subprocess",
718
self.checker_callback_tag = (gobject.child_watch_add
720
self.checker_callback,
722
# The checker may have completed before the gobject
723
# watch was added. Check for this.
724
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
726
gobject.source_remove(self.checker_callback_tag)
727
self.checker_callback(pid, status, command)
342
self.checker_callback_tag = gobject.child_watch_add\
344
self.checker_callback)
345
except subprocess.OSError, error:
346
logger.error(u"Failed to start subprocess: %s",
728
348
# Re-run this periodically if run by gobject.timeout_add
731
350
def stop_checker(self):
732
351
"""Force the checker process, if any, to stop."""
733
352
if self.checker_callback_tag:
735
354
self.checker_callback_tag = None
736
355
if getattr(self, "checker", None) is None:
738
logger.debug("Stopping checker for %(name)s", vars(self))
357
logger.debug(u"Stopping checker for %(name)s", vars(self))
740
self.checker.terminate()
359
os.kill(self.checker.pid, signal.SIGTERM)
742
361
#if self.checker.poll() is None:
743
# self.checker.kill()
744
except OSError as error:
362
# os.kill(self.checker.pid, signal.SIGKILL)
363
except OSError, error:
745
364
if error.errno != errno.ESRCH: # No such process
747
366
self.checker = None
750
def dbus_service_property(dbus_interface, signature="v",
751
access="readwrite", byte_arrays=False):
752
"""Decorators for marking methods of a DBusObjectWithProperties to
753
become properties on the D-Bus.
755
The decorated method will be called with no arguments by "Get"
756
and with one argument by "Set".
758
The parameters, where they are supported, are the same as
759
dbus.service.method, except there is only "signature", since the
760
type from Get() and the type sent to Set() is the same.
762
# Encoding deeply encoded byte arrays is not supported yet by the
763
# "Set" method, so we fail early here:
764
if byte_arrays and signature != "ay":
765
raise ValueError("Byte arrays not supported for non-'ay'"
766
" signature {0!r}".format(signature))
768
func._dbus_is_property = True
769
func._dbus_interface = dbus_interface
770
func._dbus_signature = signature
771
func._dbus_access = access
772
func._dbus_name = func.__name__
773
if func._dbus_name.endswith("_dbus_property"):
774
func._dbus_name = func._dbus_name[:-14]
775
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
780
def dbus_interface_annotations(dbus_interface):
781
"""Decorator for marking functions returning interface annotations
785
@dbus_interface_annotations("org.example.Interface")
786
def _foo(self): # Function name does not matter
787
return {"org.freedesktop.DBus.Deprecated": "true",
788
"org.freedesktop.DBus.Property.EmitsChangedSignal":
792
func._dbus_is_interface = True
793
func._dbus_interface = dbus_interface
794
func._dbus_name = dbus_interface
799
def dbus_annotations(annotations):
800
"""Decorator to annotate D-Bus methods, signals or properties
803
@dbus_service_property("org.example.Interface", signature="b",
805
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
806
"org.freedesktop.DBus.Property."
807
"EmitsChangedSignal": "false"})
808
def Property_dbus_property(self):
809
return dbus.Boolean(False)
812
func._dbus_annotations = annotations
817
class DBusPropertyException(dbus.exceptions.DBusException):
818
"""A base class for D-Bus property-related exceptions
820
def __unicode__(self):
821
return unicode(str(self))
824
class DBusPropertyAccessException(DBusPropertyException):
825
"""A property's access permissions disallows an operation.
830
class DBusPropertyNotFound(DBusPropertyException):
831
"""An attempt was made to access a non-existing property.
836
class DBusObjectWithProperties(dbus.service.Object):
837
"""A D-Bus object with properties.
839
Classes inheriting from this can use the dbus_service_property
840
decorator to expose methods as D-Bus properties. It exposes the
841
standard Get(), Set(), and GetAll() methods on the D-Bus.
845
def _is_dbus_thing(thing):
846
"""Returns a function testing if an attribute is a D-Bus thing
848
If called like _is_dbus_thing("method") it returns a function
849
suitable for use as predicate to inspect.getmembers().
851
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
854
def _get_all_dbus_things(self, thing):
855
"""Returns a generator of (name, attribute) pairs
857
return ((getattr(athing.__get__(self), "_dbus_name",
859
athing.__get__(self))
860
for cls in self.__class__.__mro__
862
inspect.getmembers(cls,
863
self._is_dbus_thing(thing)))
865
def _get_dbus_property(self, interface_name, property_name):
866
"""Returns a bound method if one exists which is a D-Bus
867
property with the specified name and interface.
869
for cls in self.__class__.__mro__:
870
for name, value in (inspect.getmembers
872
self._is_dbus_thing("property"))):
873
if (value._dbus_name == property_name
874
and value._dbus_interface == interface_name):
875
return value.__get__(self)
878
raise DBusPropertyNotFound(self.dbus_object_path + ":"
879
+ interface_name + "."
882
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
884
def Get(self, interface_name, property_name):
885
"""Standard D-Bus property Get() method, see D-Bus standard.
887
prop = self._get_dbus_property(interface_name, property_name)
888
if prop._dbus_access == "write":
889
raise DBusPropertyAccessException(property_name)
891
if not hasattr(value, "variant_level"):
893
return type(value)(value, variant_level=value.variant_level+1)
895
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
896
def Set(self, interface_name, property_name, value):
897
"""Standard D-Bus property Set() method, see D-Bus standard.
899
prop = self._get_dbus_property(interface_name, property_name)
900
if prop._dbus_access == "read":
901
raise DBusPropertyAccessException(property_name)
902
if prop._dbus_get_args_options["byte_arrays"]:
903
# The byte_arrays option is not supported yet on
904
# signatures other than "ay".
905
if prop._dbus_signature != "ay":
907
value = dbus.ByteArray(b''.join(chr(byte)
911
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
912
out_signature="a{sv}")
913
def GetAll(self, interface_name):
914
"""Standard D-Bus property GetAll() method, see D-Bus
917
Note: Will not include properties with access="write".
920
for name, prop in self._get_all_dbus_things("property"):
922
and interface_name != prop._dbus_interface):
923
# Interface non-empty but did not match
925
# Ignore write-only properties
926
if prop._dbus_access == "write":
929
if not hasattr(value, "variant_level"):
930
properties[name] = value
932
properties[name] = type(value)(value, variant_level=
933
value.variant_level+1)
934
return dbus.Dictionary(properties, signature="sv")
936
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
938
path_keyword='object_path',
939
connection_keyword='connection')
940
def Introspect(self, object_path, connection):
941
"""Overloading of standard D-Bus method.
943
Inserts property tags and interface annotation tags.
945
xmlstring = dbus.service.Object.Introspect(self, object_path,
948
document = xml.dom.minidom.parseString(xmlstring)
949
def make_tag(document, name, prop):
950
e = document.createElement("property")
951
e.setAttribute("name", name)
952
e.setAttribute("type", prop._dbus_signature)
953
e.setAttribute("access", prop._dbus_access)
955
for if_tag in document.getElementsByTagName("interface"):
957
for tag in (make_tag(document, name, prop)
959
in self._get_all_dbus_things("property")
960
if prop._dbus_interface
961
== if_tag.getAttribute("name")):
962
if_tag.appendChild(tag)
963
# Add annotation tags
964
for typ in ("method", "signal", "property"):
965
for tag in if_tag.getElementsByTagName(typ):
967
for name, prop in (self.
968
_get_all_dbus_things(typ)):
969
if (name == tag.getAttribute("name")
970
and prop._dbus_interface
971
== if_tag.getAttribute("name")):
972
annots.update(getattr
976
for name, value in annots.iteritems():
977
ann_tag = document.createElement(
979
ann_tag.setAttribute("name", name)
980
ann_tag.setAttribute("value", value)
981
tag.appendChild(ann_tag)
982
# Add interface annotation tags
983
for annotation, value in dict(
984
itertools.chain.from_iterable(
985
annotations().iteritems()
986
for name, annotations in
987
self._get_all_dbus_things("interface")
988
if name == if_tag.getAttribute("name")
990
ann_tag = document.createElement("annotation")
991
ann_tag.setAttribute("name", annotation)
992
ann_tag.setAttribute("value", value)
993
if_tag.appendChild(ann_tag)
994
# Add the names to the return values for the
995
# "org.freedesktop.DBus.Properties" methods
996
if (if_tag.getAttribute("name")
997
== "org.freedesktop.DBus.Properties"):
998
for cn in if_tag.getElementsByTagName("method"):
999
if cn.getAttribute("name") == "Get":
1000
for arg in cn.getElementsByTagName("arg"):
1001
if (arg.getAttribute("direction")
1003
arg.setAttribute("name", "value")
1004
elif cn.getAttribute("name") == "GetAll":
1005
for arg in cn.getElementsByTagName("arg"):
1006
if (arg.getAttribute("direction")
1008
arg.setAttribute("name", "props")
1009
xmlstring = document.toxml("utf-8")
1011
except (AttributeError, xml.dom.DOMException,
1012
xml.parsers.expat.ExpatError) as error:
1013
logger.error("Failed to override Introspection method",
1018
def datetime_to_dbus (dt, variant_level=0):
1019
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1021
return dbus.String("", variant_level = variant_level)
1022
return dbus.String(dt.isoformat(),
1023
variant_level=variant_level)
1026
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1027
"""A class decorator; applied to a subclass of
1028
dbus.service.Object, it will add alternate D-Bus attributes with
1029
interface names according to the "alt_interface_names" mapping.
1032
@alternate_dbus_names({"org.example.Interface":
1033
"net.example.AlternateInterface"})
1034
class SampleDBusObject(dbus.service.Object):
1035
@dbus.service.method("org.example.Interface")
1036
def SampleDBusMethod():
1039
The above "SampleDBusMethod" on "SampleDBusObject" will be
1040
reachable via two interfaces: "org.example.Interface" and
1041
"net.example.AlternateInterface", the latter of which will have
1042
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1043
"true", unless "deprecate" is passed with a False value.
1045
This works for methods and signals, and also for D-Bus properties
1046
(from DBusObjectWithProperties) and interfaces (from the
1047
dbus_interface_annotations decorator).
1050
for orig_interface_name, alt_interface_name in (
1051
alt_interface_names.iteritems()):
1053
interface_names = set()
1054
# Go though all attributes of the class
1055
for attrname, attribute in inspect.getmembers(cls):
1056
# Ignore non-D-Bus attributes, and D-Bus attributes
1057
# with the wrong interface name
1058
if (not hasattr(attribute, "_dbus_interface")
1059
or not attribute._dbus_interface
1060
.startswith(orig_interface_name)):
1062
# Create an alternate D-Bus interface name based on
1064
alt_interface = (attribute._dbus_interface
1065
.replace(orig_interface_name,
1066
alt_interface_name))
1067
interface_names.add(alt_interface)
1068
# Is this a D-Bus signal?
1069
if getattr(attribute, "_dbus_is_signal", False):
1070
# Extract the original non-method function by
1072
nonmethod_func = (dict(
1073
zip(attribute.func_code.co_freevars,
1074
attribute.__closure__))["func"]
1076
# Create a new, but exactly alike, function
1077
# object, and decorate it to be a new D-Bus signal
1078
# with the alternate D-Bus interface name
1079
new_function = (dbus.service.signal
1081
attribute._dbus_signature)
1082
(types.FunctionType(
1083
nonmethod_func.func_code,
1084
nonmethod_func.func_globals,
1085
nonmethod_func.func_name,
1086
nonmethod_func.func_defaults,
1087
nonmethod_func.func_closure)))
1088
# Copy annotations, if any
1090
new_function._dbus_annotations = (
1091
dict(attribute._dbus_annotations))
1092
except AttributeError:
1094
# Define a creator of a function to call both the
1095
# original and alternate functions, so both the
1096
# original and alternate signals gets sent when
1097
# the function is called
1098
def fixscope(func1, func2):
1099
"""This function is a scope container to pass
1100
func1 and func2 to the "call_both" function
1101
outside of its arguments"""
1102
def call_both(*args, **kwargs):
1103
"""This function will emit two D-Bus
1104
signals by calling func1 and func2"""
1105
func1(*args, **kwargs)
1106
func2(*args, **kwargs)
1108
# Create the "call_both" function and add it to
1110
attr[attrname] = fixscope(attribute, new_function)
1111
# Is this a D-Bus method?
1112
elif getattr(attribute, "_dbus_is_method", False):
1113
# Create a new, but exactly alike, function
1114
# object. Decorate it to be a new D-Bus method
1115
# with the alternate D-Bus interface name. Add it
1117
attr[attrname] = (dbus.service.method
1119
attribute._dbus_in_signature,
1120
attribute._dbus_out_signature)
1122
(attribute.func_code,
1123
attribute.func_globals,
1124
attribute.func_name,
1125
attribute.func_defaults,
1126
attribute.func_closure)))
1127
# Copy annotations, if any
1129
attr[attrname]._dbus_annotations = (
1130
dict(attribute._dbus_annotations))
1131
except AttributeError:
1133
# Is this a D-Bus property?
1134
elif getattr(attribute, "_dbus_is_property", False):
1135
# Create a new, but exactly alike, function
1136
# object, and decorate it to be a new D-Bus
1137
# property with the alternate D-Bus interface
1138
# name. Add it to the class.
1139
attr[attrname] = (dbus_service_property
1141
attribute._dbus_signature,
1142
attribute._dbus_access,
1144
._dbus_get_args_options
1147
(attribute.func_code,
1148
attribute.func_globals,
1149
attribute.func_name,
1150
attribute.func_defaults,
1151
attribute.func_closure)))
1152
# Copy annotations, if any
1154
attr[attrname]._dbus_annotations = (
1155
dict(attribute._dbus_annotations))
1156
except AttributeError:
1158
# Is this a D-Bus interface?
1159
elif getattr(attribute, "_dbus_is_interface", False):
1160
# Create a new, but exactly alike, function
1161
# object. Decorate it to be a new D-Bus interface
1162
# with the alternate D-Bus interface name. Add it
1164
attr[attrname] = (dbus_interface_annotations
1167
(attribute.func_code,
1168
attribute.func_globals,
1169
attribute.func_name,
1170
attribute.func_defaults,
1171
attribute.func_closure)))
1173
# Deprecate all alternate interfaces
1174
iname="_AlternateDBusNames_interface_annotation{0}"
1175
for interface_name in interface_names:
1176
@dbus_interface_annotations(interface_name)
1178
return { "org.freedesktop.DBus.Deprecated":
1180
# Find an unused name
1181
for aname in (iname.format(i)
1182
for i in itertools.count()):
1183
if aname not in attr:
1187
# Replace the class with a new subclass of it with
1188
# methods, signals, etc. as created above.
1189
cls = type(b"{0}Alternate".format(cls.__name__),
1195
@alternate_dbus_interfaces({"se.recompile.Mandos":
1196
"se.bsnet.fukt.Mandos"})
1197
class ClientDBus(Client, DBusObjectWithProperties):
1198
"""A Client class using D-Bus
1201
dbus_object_path: dbus.ObjectPath
1202
bus: dbus.SystemBus()
1205
runtime_expansions = (Client.runtime_expansions
1206
+ ("dbus_object_path",))
1208
# dbus.service.Object doesn't use super(), so we can't either.
1210
def __init__(self, bus = None, *args, **kwargs):
1212
Client.__init__(self, *args, **kwargs)
1213
# Only now, when this client is initialized, can it show up on
1215
client_object_name = unicode(self.name).translate(
1216
{ord("."): ord("_"),
1217
ord("-"): ord("_")})
1218
self.dbus_object_path = (dbus.ObjectPath
1219
("/clients/" + client_object_name))
1220
DBusObjectWithProperties.__init__(self, self.bus,
1221
self.dbus_object_path)
1223
def notifychangeproperty(transform_func,
1224
dbus_name, type_func=lambda x: x,
1226
""" Modify a variable so that it's a property which announces
1227
its changes to DBus.
1229
transform_fun: Function that takes a value and a variant_level
1230
and transforms it to a D-Bus type.
1231
dbus_name: D-Bus name of the variable
1232
type_func: Function that transform the value before sending it
1233
to the D-Bus. Default: no transform
1234
variant_level: D-Bus variant level. Default: 1
1236
attrname = "_{0}".format(dbus_name)
1237
def setter(self, value):
1238
if hasattr(self, "dbus_object_path"):
1239
if (not hasattr(self, attrname) or
1240
type_func(getattr(self, attrname, None))
1241
!= type_func(value)):
1242
dbus_value = transform_func(type_func(value),
1245
self.PropertyChanged(dbus.String(dbus_name),
1247
setattr(self, attrname, value)
1249
return property(lambda self: getattr(self, attrname), setter)
1251
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1252
approvals_pending = notifychangeproperty(dbus.Boolean,
1255
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1256
last_enabled = notifychangeproperty(datetime_to_dbus,
1258
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1259
type_func = lambda checker:
1260
checker is not None)
1261
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1263
last_checker_status = notifychangeproperty(dbus.Int16,
1264
"LastCheckerStatus")
1265
last_approval_request = notifychangeproperty(
1266
datetime_to_dbus, "LastApprovalRequest")
1267
approved_by_default = notifychangeproperty(dbus.Boolean,
1268
"ApprovedByDefault")
1269
approval_delay = notifychangeproperty(dbus.UInt64,
1272
timedelta_to_milliseconds)
1273
approval_duration = notifychangeproperty(
1274
dbus.UInt64, "ApprovalDuration",
1275
type_func = timedelta_to_milliseconds)
1276
host = notifychangeproperty(dbus.String, "Host")
1277
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1279
timedelta_to_milliseconds)
1280
extended_timeout = notifychangeproperty(
1281
dbus.UInt64, "ExtendedTimeout",
1282
type_func = timedelta_to_milliseconds)
1283
interval = notifychangeproperty(dbus.UInt64,
1286
timedelta_to_milliseconds)
1287
checker_command = notifychangeproperty(dbus.String, "Checker")
1289
del notifychangeproperty
1291
def __del__(self, *args, **kwargs):
1293
self.remove_from_connection()
1296
if hasattr(DBusObjectWithProperties, "__del__"):
1297
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1298
Client.__del__(self, *args, **kwargs)
1300
def checker_callback(self, pid, condition, command,
1302
self.checker_callback_tag = None
1304
if os.WIFEXITED(condition):
1305
exitstatus = os.WEXITSTATUS(condition)
1307
self.CheckerCompleted(dbus.Int16(exitstatus),
1308
dbus.Int64(condition),
1309
dbus.String(command))
1312
self.CheckerCompleted(dbus.Int16(-1),
1313
dbus.Int64(condition),
1314
dbus.String(command))
1316
return Client.checker_callback(self, pid, condition, command,
1319
def start_checker(self, *args, **kwargs):
1320
old_checker = self.checker
1321
if self.checker is not None:
1322
old_checker_pid = self.checker.pid
1324
old_checker_pid = None
1325
r = Client.start_checker(self, *args, **kwargs)
1326
# Only if new checker process was started
1327
if (self.checker is not None
1328
and old_checker_pid != self.checker.pid):
1330
self.CheckerStarted(self.current_checker_command)
1333
def _reset_approved(self):
1334
self.approved = None
1337
def approve(self, value=True):
1338
self.approved = value
1339
gobject.timeout_add(timedelta_to_milliseconds
1340
(self.approval_duration),
1341
self._reset_approved)
1342
self.send_changedstate()
1344
## D-Bus methods, signals & properties
1345
_interface = "se.recompile.Mandos.Client"
1349
@dbus_interface_annotations(_interface)
1351
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1356
# CheckerCompleted - signal
1357
@dbus.service.signal(_interface, signature="nxs")
1358
def CheckerCompleted(self, exitcode, waitstatus, command):
1362
# CheckerStarted - signal
1363
@dbus.service.signal(_interface, signature="s")
1364
def CheckerStarted(self, command):
1368
# PropertyChanged - signal
1369
@dbus.service.signal(_interface, signature="sv")
1370
def PropertyChanged(self, property, value):
1374
# GotSecret - signal
1375
@dbus.service.signal(_interface)
1376
def GotSecret(self):
1378
Is sent after a successful transfer of secret from the Mandos
1379
server to mandos-client
1384
@dbus.service.signal(_interface, signature="s")
1385
def Rejected(self, reason):
1389
# NeedApproval - signal
1390
@dbus.service.signal(_interface, signature="tb")
1391
def NeedApproval(self, timeout, default):
1393
return self.need_approval()
1398
@dbus.service.method(_interface, in_signature="b")
1399
def Approve(self, value):
1402
# CheckedOK - method
1403
@dbus.service.method(_interface)
1404
def CheckedOK(self):
1408
@dbus.service.method(_interface)
1413
# StartChecker - method
1414
@dbus.service.method(_interface)
1415
def StartChecker(self):
1417
self.start_checker()
1420
@dbus.service.method(_interface)
1425
# StopChecker - method
1426
@dbus.service.method(_interface)
1427
def StopChecker(self):
1432
# ApprovalPending - property
1433
@dbus_service_property(_interface, signature="b", access="read")
1434
def ApprovalPending_dbus_property(self):
1435
return dbus.Boolean(bool(self.approvals_pending))
1437
# ApprovedByDefault - property
1438
@dbus_service_property(_interface, signature="b",
1440
def ApprovedByDefault_dbus_property(self, value=None):
1441
if value is None: # get
1442
return dbus.Boolean(self.approved_by_default)
1443
self.approved_by_default = bool(value)
1445
# ApprovalDelay - property
1446
@dbus_service_property(_interface, signature="t",
1448
def ApprovalDelay_dbus_property(self, value=None):
1449
if value is None: # get
1450
return dbus.UInt64(self.approval_delay_milliseconds())
1451
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1453
# ApprovalDuration - property
1454
@dbus_service_property(_interface, signature="t",
1456
def ApprovalDuration_dbus_property(self, value=None):
1457
if value is None: # get
1458
return dbus.UInt64(timedelta_to_milliseconds(
1459
self.approval_duration))
1460
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1463
@dbus_service_property(_interface, signature="s", access="read")
1464
def Name_dbus_property(self):
1465
return dbus.String(self.name)
1467
# Fingerprint - property
1468
@dbus_service_property(_interface, signature="s", access="read")
1469
def Fingerprint_dbus_property(self):
1470
return dbus.String(self.fingerprint)
1473
@dbus_service_property(_interface, signature="s",
1475
def Host_dbus_property(self, value=None):
1476
if value is None: # get
1477
return dbus.String(self.host)
1478
self.host = unicode(value)
1480
# Created - property
1481
@dbus_service_property(_interface, signature="s", access="read")
1482
def Created_dbus_property(self):
1483
return datetime_to_dbus(self.created)
1485
# LastEnabled - property
1486
@dbus_service_property(_interface, signature="s", access="read")
1487
def LastEnabled_dbus_property(self):
1488
return datetime_to_dbus(self.last_enabled)
1490
# Enabled - property
1491
@dbus_service_property(_interface, signature="b",
1493
def Enabled_dbus_property(self, value=None):
1494
if value is None: # get
1495
return dbus.Boolean(self.enabled)
1501
# LastCheckedOK - property
1502
@dbus_service_property(_interface, signature="s",
1504
def LastCheckedOK_dbus_property(self, value=None):
1505
if value is not None:
1508
return datetime_to_dbus(self.last_checked_ok)
1510
# LastCheckerStatus - property
1511
@dbus_service_property(_interface, signature="n",
1513
def LastCheckerStatus_dbus_property(self):
1514
return dbus.Int16(self.last_checker_status)
1516
# Expires - property
1517
@dbus_service_property(_interface, signature="s", access="read")
1518
def Expires_dbus_property(self):
1519
return datetime_to_dbus(self.expires)
1521
# LastApprovalRequest - property
1522
@dbus_service_property(_interface, signature="s", access="read")
1523
def LastApprovalRequest_dbus_property(self):
1524
return datetime_to_dbus(self.last_approval_request)
1526
# Timeout - property
1527
@dbus_service_property(_interface, signature="t",
1529
def Timeout_dbus_property(self, value=None):
1530
if value is None: # get
1531
return dbus.UInt64(self.timeout_milliseconds())
1532
old_timeout = self.timeout
1533
self.timeout = datetime.timedelta(0, 0, 0, value)
1534
# Reschedule disabling
1536
now = datetime.datetime.utcnow()
1537
self.expires += self.timeout - old_timeout
1538
if self.expires <= now:
1539
# The timeout has passed
1542
if (getattr(self, "disable_initiator_tag", None)
1545
gobject.source_remove(self.disable_initiator_tag)
1546
self.disable_initiator_tag = (
1547
gobject.timeout_add(
1548
timedelta_to_milliseconds(self.expires - now),
1551
# ExtendedTimeout - property
1552
@dbus_service_property(_interface, signature="t",
1554
def ExtendedTimeout_dbus_property(self, value=None):
1555
if value is None: # get
1556
return dbus.UInt64(self.extended_timeout_milliseconds())
1557
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1559
# Interval - property
1560
@dbus_service_property(_interface, signature="t",
1562
def Interval_dbus_property(self, value=None):
1563
if value is None: # get
1564
return dbus.UInt64(self.interval_milliseconds())
1565
self.interval = datetime.timedelta(0, 0, 0, value)
1566
if getattr(self, "checker_initiator_tag", None) is None:
1569
# Reschedule checker run
1570
gobject.source_remove(self.checker_initiator_tag)
1571
self.checker_initiator_tag = (gobject.timeout_add
1572
(value, self.start_checker))
1573
self.start_checker() # Start one now, too
1575
# Checker - property
1576
@dbus_service_property(_interface, signature="s",
1578
def Checker_dbus_property(self, value=None):
1579
if value is None: # get
1580
return dbus.String(self.checker_command)
1581
self.checker_command = unicode(value)
1583
# CheckerRunning - property
1584
@dbus_service_property(_interface, signature="b",
1586
def CheckerRunning_dbus_property(self, value=None):
1587
if value is None: # get
1588
return dbus.Boolean(self.checker is not None)
1590
self.start_checker()
1594
# ObjectPath - property
1595
@dbus_service_property(_interface, signature="o", access="read")
1596
def ObjectPath_dbus_property(self):
1597
return self.dbus_object_path # is already a dbus.ObjectPath
1600
@dbus_service_property(_interface, signature="ay",
1601
access="write", byte_arrays=True)
1602
def Secret_dbus_property(self, value):
1603
self.secret = str(value)
1608
class ProxyClient(object):
1609
def __init__(self, child_pipe, fpr, address):
1610
self._pipe = child_pipe
1611
self._pipe.send(('init', fpr, address))
1612
if not self._pipe.recv():
1615
def __getattribute__(self, name):
1617
return super(ProxyClient, self).__getattribute__(name)
1618
self._pipe.send(('getattr', name))
1619
data = self._pipe.recv()
1620
if data[0] == 'data':
1622
if data[0] == 'function':
1623
def func(*args, **kwargs):
1624
self._pipe.send(('funcall', name, args, kwargs))
1625
return self._pipe.recv()[1]
1628
def __setattr__(self, name, value):
1630
return super(ProxyClient, self).__setattr__(name, value)
1631
self._pipe.send(('setattr', name, value))
1634
class ClientHandler(socketserver.BaseRequestHandler, object):
1635
"""A class to handle client connections.
1637
Instantiated once for each connection to handle it.
367
def still_valid(self):
368
"""Has the timeout not yet passed for this client?"""
369
now = datetime.datetime.now()
370
if self.last_checked_ok is None:
371
return now < (self.created + self.timeout)
373
return now < (self.last_checked_ok + self.timeout)
376
def peer_certificate(session):
377
"Return the peer's OpenPGP certificate as a bytestring"
378
# If not an OpenPGP certificate...
379
if gnutls.library.functions.gnutls_certificate_type_get\
380
(session._c_object) \
381
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
382
# ...do the normal thing
383
return session.peer_certificate
384
list_size = ctypes.c_uint()
385
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
386
(session._c_object, ctypes.byref(list_size))
387
if list_size.value == 0:
390
return ctypes.string_at(cert.data, cert.size)
393
def fingerprint(openpgp):
394
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
395
# New GnuTLS "datum" with the OpenPGP public key
396
datum = gnutls.library.types.gnutls_datum_t\
397
(ctypes.cast(ctypes.c_char_p(openpgp),
398
ctypes.POINTER(ctypes.c_ubyte)),
399
ctypes.c_uint(len(openpgp)))
400
# New empty GnuTLS certificate
401
crt = gnutls.library.types.gnutls_openpgp_crt_t()
402
gnutls.library.functions.gnutls_openpgp_crt_init\
404
# Import the OpenPGP public key into the certificate
405
gnutls.library.functions.gnutls_openpgp_crt_import\
406
(crt, ctypes.byref(datum),
407
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
408
# New buffer for the fingerprint
409
buffer = ctypes.create_string_buffer(20)
410
buffer_length = ctypes.c_size_t()
411
# Get the fingerprint from the certificate into the buffer
412
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
413
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
414
# Deinit the certificate
415
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
416
# Convert the buffer to a Python bytestring
417
fpr = ctypes.string_at(buffer, buffer_length.value)
418
# Convert the bytestring to hexadecimal notation
419
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
423
class tcp_handler(SocketServer.BaseRequestHandler, object):
424
"""A TCP request handler class.
425
Instantiated by IPv6_TCPServer for each request to handle it.
1638
426
Note: This will run in its own forked process."""
1640
428
def handle(self):
1641
with contextlib.closing(self.server.child_pipe) as child_pipe:
1642
logger.info("TCP connection from: %s",
1643
unicode(self.client_address))
1644
logger.debug("Pipe FD: %d",
1645
self.server.child_pipe.fileno())
1647
session = (gnutls.connection
1648
.ClientSession(self.request,
1650
.X509Credentials()))
1652
# Note: gnutls.connection.X509Credentials is really a
1653
# generic GnuTLS certificate credentials object so long as
1654
# no X.509 keys are added to it. Therefore, we can use it
1655
# here despite using OpenPGP certificates.
1657
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1658
# "+AES-256-CBC", "+SHA1",
1659
# "+COMP-NULL", "+CTYPE-OPENPGP",
1661
# Use a fallback default, since this MUST be set.
1662
priority = self.server.gnutls_priority
1663
if priority is None:
1665
(gnutls.library.functions
1666
.gnutls_priority_set_direct(session._c_object,
1669
# Start communication using the Mandos protocol
1670
# Get protocol number
1671
line = self.request.makefile().readline()
1672
logger.debug("Protocol version: %r", line)
1674
if int(line.strip().split()[0]) > 1:
1676
except (ValueError, IndexError, RuntimeError) as error:
1677
logger.error("Unknown protocol version: %s", error)
1680
# Start GnuTLS connection
1683
except gnutls.errors.GNUTLSError as error:
1684
logger.warning("Handshake failed: %s", error)
1685
# Do not run session.bye() here: the session is not
1686
# established. Just abandon the request.
1688
logger.debug("Handshake succeeded")
1690
approval_required = False
1693
fpr = self.fingerprint(self.peer_certificate
1696
gnutls.errors.GNUTLSError) as error:
1697
logger.warning("Bad certificate: %s", error)
1699
logger.debug("Fingerprint: %s", fpr)
1702
client = ProxyClient(child_pipe, fpr,
1703
self.client_address)
1707
if client.approval_delay:
1708
delay = client.approval_delay
1709
client.approvals_pending += 1
1710
approval_required = True
1713
if not client.enabled:
1714
logger.info("Client %s is disabled",
1716
if self.server.use_dbus:
1718
client.Rejected("Disabled")
1721
if client.approved or not client.approval_delay:
1722
#We are approved or approval is disabled
1724
elif client.approved is None:
1725
logger.info("Client %s needs approval",
1727
if self.server.use_dbus:
1729
client.NeedApproval(
1730
client.approval_delay_milliseconds(),
1731
client.approved_by_default)
1733
logger.warning("Client %s was not approved",
1735
if self.server.use_dbus:
1737
client.Rejected("Denied")
1740
#wait until timeout or approved
1741
time = datetime.datetime.now()
1742
client.changedstate.acquire()
1743
client.changedstate.wait(
1744
float(timedelta_to_milliseconds(delay)
1746
client.changedstate.release()
1747
time2 = datetime.datetime.now()
1748
if (time2 - time) >= delay:
1749
if not client.approved_by_default:
1750
logger.warning("Client %s timed out while"
1751
" waiting for approval",
1753
if self.server.use_dbus:
1755
client.Rejected("Approval timed out")
1760
delay -= time2 - time
1763
while sent_size < len(client.secret):
1765
sent = session.send(client.secret[sent_size:])
1766
except gnutls.errors.GNUTLSError as error:
1767
logger.warning("gnutls send failed",
1770
logger.debug("Sent: %d, remaining: %d",
1771
sent, len(client.secret)
1772
- (sent_size + sent))
1775
logger.info("Sending secret to %s", client.name)
1776
# bump the timeout using extended_timeout
1777
client.bump_timeout(client.extended_timeout)
1778
if self.server.use_dbus:
1783
if approval_required:
1784
client.approvals_pending -= 1
1787
except gnutls.errors.GNUTLSError as error:
1788
logger.warning("GnuTLS bye failed",
1792
def peer_certificate(session):
1793
"Return the peer's OpenPGP certificate as a bytestring"
1794
# If not an OpenPGP certificate...
1795
if (gnutls.library.functions
1796
.gnutls_certificate_type_get(session._c_object)
1797
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1798
# ...do the normal thing
1799
return session.peer_certificate
1800
list_size = ctypes.c_uint(1)
1801
cert_list = (gnutls.library.functions
1802
.gnutls_certificate_get_peers
1803
(session._c_object, ctypes.byref(list_size)))
1804
if not bool(cert_list) and list_size.value != 0:
1805
raise gnutls.errors.GNUTLSError("error getting peer"
1807
if list_size.value == 0:
1810
return ctypes.string_at(cert.data, cert.size)
1813
def fingerprint(openpgp):
1814
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1815
# New GnuTLS "datum" with the OpenPGP public key
1816
datum = (gnutls.library.types
1817
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1820
ctypes.c_uint(len(openpgp))))
1821
# New empty GnuTLS certificate
1822
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1823
(gnutls.library.functions
1824
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1825
# Import the OpenPGP public key into the certificate
1826
(gnutls.library.functions
1827
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1828
gnutls.library.constants
1829
.GNUTLS_OPENPGP_FMT_RAW))
1830
# Verify the self signature in the key
1831
crtverify = ctypes.c_uint()
1832
(gnutls.library.functions
1833
.gnutls_openpgp_crt_verify_self(crt, 0,
1834
ctypes.byref(crtverify)))
1835
if crtverify.value != 0:
1836
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1837
raise (gnutls.errors.CertificateSecurityError
1839
# New buffer for the fingerprint
1840
buf = ctypes.create_string_buffer(20)
1841
buf_len = ctypes.c_size_t()
1842
# Get the fingerprint from the certificate into the buffer
1843
(gnutls.library.functions
1844
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1845
ctypes.byref(buf_len)))
1846
# Deinit the certificate
1847
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1848
# Convert the buffer to a Python bytestring
1849
fpr = ctypes.string_at(buf, buf_len.value)
1850
# Convert the bytestring to hexadecimal notation
1851
hex_fpr = binascii.hexlify(fpr).upper()
1855
class MultiprocessingMixIn(object):
1856
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1857
def sub_process_main(self, request, address):
1859
self.finish_request(request, address)
1861
self.handle_error(request, address)
1862
self.close_request(request)
1864
def process_request(self, request, address):
1865
"""Start a new process to process the request."""
1866
proc = multiprocessing.Process(target = self.sub_process_main,
1867
args = (request, address))
1872
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1873
""" adds a pipe to the MixIn """
1874
def process_request(self, request, client_address):
1875
"""Overrides and wraps the original process_request().
1877
This function creates a new pipe in self.pipe
1879
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1881
proc = MultiprocessingMixIn.process_request(self, request,
1883
self.child_pipe.close()
1884
self.add_pipe(parent_pipe, proc)
1886
def add_pipe(self, parent_pipe, proc):
1887
"""Dummy function; override as necessary"""
1888
raise NotImplementedError
1891
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1892
socketserver.TCPServer, object):
1893
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
429
logger.info(u"TCP connection from: %s",
430
unicode(self.client_address))
431
session = gnutls.connection.ClientSession\
432
(self.request, gnutls.connection.X509Credentials())
434
line = self.request.makefile().readline()
435
logger.debug(u"Protocol version: %r", line)
437
if int(line.strip().split()[0]) > 1:
439
except (ValueError, IndexError, RuntimeError), error:
440
logger.error(u"Unknown protocol version: %s", error)
443
# Note: gnutls.connection.X509Credentials is really a generic
444
# GnuTLS certificate credentials object so long as no X.509
445
# keys are added to it. Therefore, we can use it here despite
446
# using OpenPGP certificates.
448
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
449
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
451
priority = "NORMAL" # Fallback default, since this
453
if self.server.settings["priority"]:
454
priority = self.server.settings["priority"]
455
gnutls.library.functions.gnutls_priority_set_direct\
456
(session._c_object, priority, None);
460
except gnutls.errors.GNUTLSError, error:
461
logger.warning(u"Handshake failed: %s", error)
462
# Do not run session.bye() here: the session is not
463
# established. Just abandon the request.
466
fpr = fingerprint(peer_certificate(session))
467
except (TypeError, gnutls.errors.GNUTLSError), error:
468
logger.warning(u"Bad certificate: %s", error)
471
logger.debug(u"Fingerprint: %s", fpr)
473
for c in self.server.clients:
474
if c.fingerprint == fpr:
478
logger.warning(u"Client not found for fingerprint: %s",
482
# Have to check if client.still_valid(), since it is possible
483
# that the client timed out while establishing the GnuTLS
485
if not client.still_valid():
486
logger.warning(u"Client %(name)s is invalid",
491
while sent_size < len(client.secret):
492
sent = session.send(client.secret[sent_size:])
493
logger.debug(u"Sent: %d, remaining: %d",
494
sent, len(client.secret)
495
- (sent_size + sent))
500
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
501
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1896
enabled: Boolean; whether this server is activated yet
1897
interface: None or a network interface name (string)
1898
use_ipv6: Boolean; to use IPv6 or not
503
settings: Server settings
504
clients: Set() of Client objects
1900
def __init__(self, server_address, RequestHandlerClass,
1901
interface=None, use_ipv6=True):
1902
self.interface = interface
1904
self.address_family = socket.AF_INET6
1905
socketserver.TCPServer.__init__(self, server_address,
1906
RequestHandlerClass)
506
address_family = socket.AF_INET6
507
def __init__(self, *args, **kwargs):
508
if "settings" in kwargs:
509
self.settings = kwargs["settings"]
510
del kwargs["settings"]
511
if "clients" in kwargs:
512
self.clients = kwargs["clients"]
513
del kwargs["clients"]
514
return super(type(self), self).__init__(*args, **kwargs)
1907
515
def server_bind(self):
1908
516
"""This overrides the normal server_bind() function
1909
517
to bind to an interface if one was specified, and also NOT to
1910
518
bind to an address or port if they were not specified."""
1911
if self.interface is not None:
1912
if SO_BINDTODEVICE is None:
1913
logger.error("SO_BINDTODEVICE does not exist;"
1914
" cannot bind to interface %s",
1918
self.socket.setsockopt(socket.SOL_SOCKET,
1922
except socket.error as error:
1923
if error.errno == errno.EPERM:
1924
logger.error("No permission to"
1925
" bind to interface %s",
1927
elif error.errno == errno.ENOPROTOOPT:
1928
logger.error("SO_BINDTODEVICE not available;"
1929
" cannot bind to interface %s",
1931
elif error.errno == errno.ENODEV:
1932
logger.error("Interface %s does not"
1933
" exist, cannot bind",
519
if self.settings["interface"]:
520
# 25 is from /usr/include/asm-i486/socket.h
521
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
523
self.socket.setsockopt(socket.SOL_SOCKET,
525
self.settings["interface"])
526
except socket.error, error:
527
if error[0] == errno.EPERM:
528
logger.error(u"No permission to"
529
u" bind to interface %s",
530
self.settings["interface"])
1937
533
# Only bind(2) the socket if we really need to.
1938
534
if self.server_address[0] or self.server_address[1]:
1939
535
if not self.server_address[0]:
1940
if self.address_family == socket.AF_INET6:
1941
any_address = "::" # in6addr_any
1943
any_address = socket.INADDR_ANY
1944
self.server_address = (any_address,
537
self.server_address = (in6addr_any,
1945
538
self.server_address[1])
1946
539
elif not self.server_address[1]:
1947
540
self.server_address = (self.server_address[0],
1949
# if self.interface:
542
# if self.settings["interface"]:
1950
543
# self.server_address = (self.server_address[0],
1953
546
# if_nametoindex
1955
return socketserver.TCPServer.server_bind(self)
1958
class MandosServer(IPv6_TCPServer):
1962
clients: set of Client objects
1963
gnutls_priority GnuTLS priority string
1964
use_dbus: Boolean; to emit D-Bus signals or not
1966
Assumes a gobject.MainLoop event loop.
1968
def __init__(self, server_address, RequestHandlerClass,
1969
interface=None, use_ipv6=True, clients=None,
1970
gnutls_priority=None, use_dbus=True):
1971
self.enabled = False
1972
self.clients = clients
1973
if self.clients is None:
1975
self.use_dbus = use_dbus
1976
self.gnutls_priority = gnutls_priority
1977
IPv6_TCPServer.__init__(self, server_address,
1978
RequestHandlerClass,
1979
interface = interface,
1980
use_ipv6 = use_ipv6)
1981
def server_activate(self):
1983
return socketserver.TCPServer.server_activate(self)
1988
def add_pipe(self, parent_pipe, proc):
1989
# Call "handle_ipc" for both data and EOF events
1990
gobject.io_add_watch(parent_pipe.fileno(),
1991
gobject.IO_IN | gobject.IO_HUP,
1992
functools.partial(self.handle_ipc,
1997
def handle_ipc(self, source, condition, parent_pipe=None,
1998
proc = None, client_object=None):
1999
# error, or the other end of multiprocessing.Pipe has closed
2000
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2001
# Wait for other process to exit
2005
# Read a request from the child
2006
request = parent_pipe.recv()
2007
command = request[0]
2009
if command == 'init':
2011
address = request[2]
2013
for c in self.clients.itervalues():
2014
if c.fingerprint == fpr:
2018
logger.info("Client not found for fingerprint: %s, ad"
2019
"dress: %s", fpr, address)
2022
mandos_dbus_service.ClientNotFound(fpr,
2024
parent_pipe.send(False)
2027
gobject.io_add_watch(parent_pipe.fileno(),
2028
gobject.IO_IN | gobject.IO_HUP,
2029
functools.partial(self.handle_ipc,
2035
parent_pipe.send(True)
2036
# remove the old hook in favor of the new above hook on
2039
if command == 'funcall':
2040
funcname = request[1]
2044
parent_pipe.send(('data', getattr(client_object,
2048
if command == 'getattr':
2049
attrname = request[1]
2050
if callable(client_object.__getattribute__(attrname)):
2051
parent_pipe.send(('function',))
2053
parent_pipe.send(('data', client_object
2054
.__getattribute__(attrname)))
2056
if command == 'setattr':
2057
attrname = request[1]
2059
setattr(client_object, attrname, value)
549
return super(type(self), self).server_bind()
2064
552
def string_to_delta(interval):
2065
553
"""Parse a string and return a datetime.timedelta
2067
555
>>> string_to_delta('7d')
2068
556
datetime.timedelta(7)
2069
557
>>> string_to_delta('60s')
2183
694
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2184
695
"servicename": "Mandos",
2189
"statedir": "/var/lib/mandos"
2192
698
# Parse config file for server-global settings
2193
server_config = configparser.SafeConfigParser(server_defaults)
699
server_config = ConfigParser.SafeConfigParser(server_defaults)
2194
700
del server_defaults
2195
server_config.read(os.path.join(options.configdir,
701
server_config.read(os.path.join(options.configdir, "mandos.conf"))
702
server_section = "server"
2197
703
# Convert the SafeConfigParser object to a dict
2198
server_settings = server_config.defaults()
2199
# Use the appropriate methods on the non-string config options
2200
for option in ("debug", "use_dbus", "use_ipv6"):
2201
server_settings[option] = server_config.getboolean("DEFAULT",
2203
if server_settings["port"]:
2204
server_settings["port"] = server_config.getint("DEFAULT",
704
server_settings = dict(server_config.items(server_section))
705
# Use getboolean on the boolean config option
706
server_settings["debug"] = server_config.getboolean\
707
(server_section, "debug")
2206
708
del server_config
2208
710
# Override the settings from the config file with command line
2209
711
# options, if set.
2210
712
for option in ("interface", "address", "port", "debug",
2211
"priority", "servicename", "configdir",
2212
"use_dbus", "use_ipv6", "debuglevel", "restore",
713
"priority", "servicename", "configdir"):
2214
714
value = getattr(options, option)
2215
715
if value is not None:
2216
716
server_settings[option] = value
2218
# Force all strings to be unicode
2219
for option in server_settings.keys():
2220
if type(server_settings[option]) is str:
2221
server_settings[option] = unicode(server_settings[option])
2222
718
# Now we have our good server settings in "server_settings"
2224
##################################################################
2227
720
debug = server_settings["debug"]
2228
debuglevel = server_settings["debuglevel"]
2229
use_dbus = server_settings["use_dbus"]
2230
use_ipv6 = server_settings["use_ipv6"]
2231
stored_state_path = os.path.join(server_settings["statedir"],
2235
initlogger(debug, logging.DEBUG)
2240
level = getattr(logging, debuglevel.upper())
2241
initlogger(debug, level)
723
syslogger.setLevel(logging.WARNING)
724
console.setLevel(logging.WARNING)
2243
726
if server_settings["servicename"] != "Mandos":
2244
syslogger.setFormatter(logging.Formatter
2245
('Mandos ({0}) [%(process)d]:'
2246
' %(levelname)s: %(message)s'
2247
.format(server_settings
727
syslogger.setFormatter(logging.Formatter\
728
('Mandos (%s): %%(levelname)s:'
730
% server_settings["servicename"]))
2250
732
# Parse config file with clients
2251
client_config = configparser.SafeConfigParser(Client
733
client_defaults = { "timeout": "1h",
735
"checker": "fping -q -- %%(host)s",
737
client_config = ConfigParser.SafeConfigParser(client_defaults)
2253
738
client_config.read(os.path.join(server_settings["configdir"],
2254
739
"clients.conf"))
2256
global mandos_dbus_service
2257
mandos_dbus_service = None
2259
tcp_server = MandosServer((server_settings["address"],
2260
server_settings["port"]),
2262
interface=(server_settings["interface"]
2266
server_settings["priority"],
2269
pidfilename = "/var/run/mandos.pid"
2271
pidfile = open(pidfilename, "w")
2272
except IOError as e:
2273
logger.error("Could not open file %r", pidfilename,
2276
for name in ("_mandos", "mandos", "nobody"):
2278
uid = pwd.getpwnam(name).pw_uid
2279
gid = pwd.getpwnam(name).pw_gid
2289
except OSError as error:
2290
if error.errno != errno.EPERM:
2294
# Enable all possible GnuTLS debugging
2296
# "Use a log level over 10 to enable all debugging options."
2298
gnutls.library.functions.gnutls_global_set_log_level(11)
2300
@gnutls.library.types.gnutls_log_func
2301
def debug_gnutls(level, string):
2302
logger.debug("GnuTLS: %s", string[:-1])
2304
(gnutls.library.functions
2305
.gnutls_global_set_log_function(debug_gnutls))
2307
# Redirect stdin so all checkers get /dev/null
2308
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2309
os.dup2(null, sys.stdin.fileno())
2313
# Need to fork before connecting to D-Bus
2315
# Close all input and output, do double fork, etc.
2318
gobject.threads_init()
742
service = AvahiService(name = server_settings["servicename"],
743
type = "_mandos._tcp", );
744
if server_settings["interface"]:
745
service.interface = if_nametoindex(server_settings["interface"])
2320
747
global main_loop
2321
750
# From the Avahi example code
2322
DBusGMainLoop(set_as_default=True)
751
DBusGMainLoop(set_as_default=True )
2323
752
main_loop = gobject.MainLoop()
2324
753
bus = dbus.SystemBus()
754
server = dbus.Interface(
755
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
756
avahi.DBUS_INTERFACE_SERVER )
2325
757
# End of Avahi example code
2328
bus_name = dbus.service.BusName("se.recompile.Mandos",
2329
bus, do_not_queue=True)
2330
old_bus_name = (dbus.service.BusName
2331
("se.bsnet.fukt.Mandos", bus,
2333
except dbus.exceptions.NameExistsException as e:
2334
logger.error("Disabling D-Bus:", exc_info=e)
2336
server_settings["use_dbus"] = False
2337
tcp_server.use_dbus = False
2338
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2339
service = AvahiServiceToSyslog(name =
2340
server_settings["servicename"],
2341
servicetype = "_mandos._tcp",
2342
protocol = protocol, bus = bus)
2343
if server_settings["interface"]:
2344
service.interface = (if_nametoindex
2345
(str(server_settings["interface"])))
2347
global multiprocessing_manager
2348
multiprocessing_manager = multiprocessing.Manager()
2350
client_class = Client
2352
client_class = functools.partial(ClientDBus, bus = bus)
2354
client_settings = Client.config_parser(client_config)
2355
old_client_settings = {}
2358
# Get client data and settings from last running state.
2359
if server_settings["restore"]:
2361
with open(stored_state_path, "rb") as stored_state:
2362
clients_data, old_client_settings = (pickle.load
2364
os.remove(stored_state_path)
2365
except IOError as e:
2366
if e.errno == errno.ENOENT:
2367
logger.warning("Could not load persistent state: {0}"
2368
.format(os.strerror(e.errno)))
2370
logger.critical("Could not load persistent state:",
2373
except EOFError as e:
2374
logger.warning("Could not load persistent state: "
2375
"EOFError:", exc_info=e)
2377
with PGPEngine() as pgp:
2378
for client_name, client in clients_data.iteritems():
2379
# Decide which value to use after restoring saved state.
2380
# We have three different values: Old config file,
2381
# new config file, and saved state.
2382
# New config value takes precedence if it differs from old
2383
# config value, otherwise use saved state.
2384
for name, value in client_settings[client_name].items():
2386
# For each value in new config, check if it
2387
# differs from the old config value (Except for
2388
# the "secret" attribute)
2389
if (name != "secret" and
2390
value != old_client_settings[client_name]
2392
client[name] = value
2396
# Clients who has passed its expire date can still be
2397
# enabled if its last checker was successful. Clients
2398
# whose checker succeeded before we stored its state is
2399
# assumed to have successfully run all checkers during
2401
if client["enabled"]:
2402
if datetime.datetime.utcnow() >= client["expires"]:
2403
if not client["last_checked_ok"]:
2405
"disabling client {0} - Client never "
2406
"performed a successful checker"
2407
.format(client_name))
2408
client["enabled"] = False
2409
elif client["last_checker_status"] != 0:
2411
"disabling client {0} - Client "
2412
"last checker failed with error code {1}"
2413
.format(client_name,
2414
client["last_checker_status"]))
2415
client["enabled"] = False
2417
client["expires"] = (datetime.datetime
2419
+ client["timeout"])
2420
logger.debug("Last checker succeeded,"
2421
" keeping {0} enabled"
2422
.format(client_name))
2424
client["secret"] = (
2425
pgp.decrypt(client["encrypted_secret"],
2426
client_settings[client_name]
2429
# If decryption fails, we use secret from new settings
2430
logger.debug("Failed to decrypt {0} old secret"
2431
.format(client_name))
2432
client["secret"] = (
2433
client_settings[client_name]["secret"])
2435
# Add/remove clients based on new changes made to config
2436
for client_name in (set(old_client_settings)
2437
- set(client_settings)):
2438
del clients_data[client_name]
2439
for client_name in (set(client_settings)
2440
- set(old_client_settings)):
2441
clients_data[client_name] = client_settings[client_name]
2443
# Create all client objects
2444
for client_name, client in clients_data.iteritems():
2445
tcp_server.clients[client_name] = client_class(
2446
name = client_name, settings = client)
2448
if not tcp_server.clients:
2449
logger.warning("No clients defined")
2455
pidfile.write(str(pid) + "\n".encode("utf-8"))
2458
logger.error("Could not write to file %r with PID %d",
2461
# "pidfile" was never created
760
def remove_from_clients(client):
761
clients.remove(client)
763
logger.critical(u"No clients left, exiting")
766
clients.update(Set(Client(name = section,
767
stop_hook = remove_from_clients,
769
= dict(client_config.items(section)))
770
for section in client_config.sections()))
772
logger.critical(u"No clients defined")
776
logger.removeHandler(console)
779
pidfilename = "/var/run/mandos/mandos.pid"
782
pidfile = open(pidfilename, "w")
783
pidfile.write(str(pid) + "\n")
787
logger.error(u"Could not write %s file with PID %d",
788
pidfilename, os.getpid())
791
"Cleanup function; run on exit"
793
# From the Avahi example code
794
if not group is None:
797
# End of Avahi example code
800
client = clients.pop()
801
client.stop_hook = None
804
atexit.register(cleanup)
2464
807
signal.signal(signal.SIGINT, signal.SIG_IGN)
2466
808
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2467
809
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2470
@alternate_dbus_interfaces({"se.recompile.Mandos":
2471
"se.bsnet.fukt.Mandos"})
2472
class MandosDBusService(DBusObjectWithProperties):
2473
"""A D-Bus proxy object"""
2475
dbus.service.Object.__init__(self, bus, "/")
2476
_interface = "se.recompile.Mandos"
2478
@dbus_interface_annotations(_interface)
2480
return { "org.freedesktop.DBus.Property"
2481
".EmitsChangedSignal":
2484
@dbus.service.signal(_interface, signature="o")
2485
def ClientAdded(self, objpath):
2489
@dbus.service.signal(_interface, signature="ss")
2490
def ClientNotFound(self, fingerprint, address):
2494
@dbus.service.signal(_interface, signature="os")
2495
def ClientRemoved(self, objpath, name):
2499
@dbus.service.method(_interface, out_signature="ao")
2500
def GetAllClients(self):
2502
return dbus.Array(c.dbus_object_path
2504
tcp_server.clients.itervalues())
2506
@dbus.service.method(_interface,
2507
out_signature="a{oa{sv}}")
2508
def GetAllClientsWithProperties(self):
2510
return dbus.Dictionary(
2511
((c.dbus_object_path, c.GetAll(""))
2512
for c in tcp_server.clients.itervalues()),
2515
@dbus.service.method(_interface, in_signature="o")
2516
def RemoveClient(self, object_path):
2518
for c in tcp_server.clients.itervalues():
2519
if c.dbus_object_path == object_path:
2520
del tcp_server.clients[c.name]
2521
c.remove_from_connection()
2522
# Don't signal anything except ClientRemoved
2523
c.disable(quiet=True)
2525
self.ClientRemoved(object_path, c.name)
2527
raise KeyError(object_path)
2531
mandos_dbus_service = MandosDBusService()
2534
"Cleanup function; run on exit"
2537
multiprocessing.active_children()
2538
if not (tcp_server.clients or client_settings):
2541
# Store client before exiting. Secrets are encrypted with key
2542
# based on what config file has. If config file is
2543
# removed/edited, old secret will thus be unrecovable.
2545
with PGPEngine() as pgp:
2546
for client in tcp_server.clients.itervalues():
2547
key = client_settings[client.name]["secret"]
2548
client.encrypted_secret = pgp.encrypt(client.secret,
2552
# A list of attributes that can not be pickled
2554
exclude = set(("bus", "changedstate", "secret",
2556
for name, typ in (inspect.getmembers
2557
(dbus.service.Object)):
2560
client_dict["encrypted_secret"] = (client
2562
for attr in client.client_structure:
2563
if attr not in exclude:
2564
client_dict[attr] = getattr(client, attr)
2566
clients[client.name] = client_dict
2567
del client_settings[client.name]["secret"]
2570
with (tempfile.NamedTemporaryFile
2571
(mode='wb', suffix=".pickle", prefix='clients-',
2572
dir=os.path.dirname(stored_state_path),
2573
delete=False)) as stored_state:
2574
pickle.dump((clients, client_settings), stored_state)
2575
tempname=stored_state.name
2576
os.rename(tempname, stored_state_path)
2577
except (IOError, OSError) as e:
2583
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2584
logger.warning("Could not save persistent state: {0}"
2585
.format(os.strerror(e.errno)))
2587
logger.warning("Could not save persistent state:",
2591
# Delete all clients, and settings from config
2592
while tcp_server.clients:
2593
name, client = tcp_server.clients.popitem()
2595
client.remove_from_connection()
2596
# Don't signal anything except ClientRemoved
2597
client.disable(quiet=True)
2600
mandos_dbus_service.ClientRemoved(client
2603
client_settings.clear()
2605
atexit.register(cleanup)
2607
for client in tcp_server.clients.itervalues():
2610
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2611
# Need to initiate checking of clients
2613
client.init_checker()
2616
tcp_server.server_activate()
811
for client in clients:
814
tcp_server = IPv6_TCPServer((server_settings["address"],
815
server_settings["port"]),
817
settings=server_settings,
2618
819
# Find out what port we got
2619
820
service.port = tcp_server.socket.getsockname()[1]
2621
logger.info("Now listening on address %r, port %d,"
2622
" flowinfo %d, scope_id %d",
2623
*tcp_server.socket.getsockname())
2625
logger.info("Now listening on address %r, port %d",
2626
*tcp_server.socket.getsockname())
821
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
822
u" scope_id %d" % tcp_server.socket.getsockname())
2628
824
#service.interface = tcp_server.socket.getsockname()[3]
2631
827
# From the Avahi example code
828
server.connect_to_signal("StateChanged", server_state_changed)
2634
except dbus.exceptions.DBusException as error:
2635
logger.critical("D-Bus Exception", exc_info=error)
830
server_state_changed(server.GetState())
831
except dbus.exceptions.DBusException, error:
832
logger.critical(u"DBusException: %s", error)
2638
834
# End of Avahi example code
2640
836
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2641
837
lambda *args, **kwargs:
2642
(tcp_server.handle_request
2643
(*args[2:], **kwargs) or True))
838
tcp_server.handle_request\
839
(*args[2:], **kwargs) or True)
2645
logger.debug("Starting main loop")
841
logger.debug(u"Starting main loop")
842
main_loop_started = True
2647
except AvahiError as error:
2648
logger.critical("Avahi Error", exc_info=error)
844
except AvahiError, error:
845
logger.critical(u"AvahiError: %s" + unicode(error))
2651
847
except KeyboardInterrupt:
2653
print("", file=sys.stderr)
2654
logger.debug("Server received KeyboardInterrupt")
2655
logger.debug("Server exiting")
2656
# Must run before the D-Bus bus name gets deregistered
2659
851
if __name__ == '__main__':