241
107
max_renames: integer; maximum number of renames
242
108
rename_count: integer; counter so we only rename after collisions
243
109
a sensible number of times
244
group: D-Bus Entry Group
246
bus: dbus.SystemBus()
249
111
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
250
servicetype = None, port = None, TXT = None,
251
domain = "", host = "", max_renames = 32768,
252
protocol = avahi.PROTO_UNSPEC, bus = None):
112
servicetype = None, port = None, TXT = None, domain = "",
113
host = "", max_renames = 32768):
253
114
self.interface = interface
255
116
self.type = servicetype
257
self.TXT = TXT if TXT is not None else []
258
122
self.domain = domain
260
124
self.rename_count = 0
261
125
self.max_renames = max_renames
262
self.protocol = protocol
263
self.group = None # our entry group
266
self.entry_group_state_changed_match = None
268
126
def rename(self):
269
127
"""Derived from the Avahi example code"""
270
128
if self.rename_count >= self.max_renames:
271
logger.critical("No suitable Zeroconf service name found"
272
" after %i retries, exiting.",
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
273
131
self.rename_count)
274
132
raise AvahiServiceError("Too many renames")
275
self.name = unicode(self.server
276
.GetAlternativeServiceName(self.name))
277
logger.info("Changing Zeroconf service name to %r ...",
133
self.name = server.GetAlternativeServiceName(self.name)
134
logger.info(u"Changing Zeroconf service name to %r ...",
136
syslogger.setFormatter(logging.Formatter\
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
282
except dbus.exceptions.DBusException as error:
283
logger.critical("D-Bus Exception", exc_info=error)
286
141
self.rename_count += 1
288
142
def remove(self):
289
143
"""Derived from the Avahi example code"""
290
if self.entry_group_state_changed_match is not None:
291
self.entry_group_state_changed_match.remove()
292
self.entry_group_state_changed_match = None
293
if self.group is not None:
144
if group is not None:
297
147
"""Derived from the Avahi example code"""
299
if self.group is None:
300
self.group = dbus.Interface(
301
self.bus.get_object(avahi.DBUS_NAME,
302
self.server.EntryGroupNew()),
303
avahi.DBUS_INTERFACE_ENTRY_GROUP)
304
self.entry_group_state_changed_match = (
305
self.group.connect_to_signal(
306
'StateChanged', self.entry_group_state_changed))
307
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
308
self.name, self.type)
309
self.group.AddService(
312
dbus.UInt32(0), # flags
313
self.name, self.type,
314
self.domain, self.host,
315
dbus.UInt16(self.port),
316
avahi.string_array_to_txt_array(self.TXT))
319
def entry_group_state_changed(self, state, error):
320
"""Derived from the Avahi example code"""
321
logger.debug("Avahi entry group state change: %i", state)
323
if state == avahi.ENTRY_GROUP_ESTABLISHED:
324
logger.debug("Zeroconf service established.")
325
elif state == avahi.ENTRY_GROUP_COLLISION:
326
logger.info("Zeroconf service name collision.")
328
elif state == avahi.ENTRY_GROUP_FAILURE:
329
logger.critical("Avahi: Error in group state changed %s",
331
raise AvahiGroupError("State changed: {0!s}"
335
"""Derived from the Avahi example code"""
336
if self.group is not None:
339
except (dbus.exceptions.UnknownMethodException,
340
dbus.exceptions.DBusException):
345
def server_state_changed(self, state, error=None):
346
"""Derived from the Avahi example code"""
347
logger.debug("Avahi server state change: %i", state)
348
bad_states = { avahi.SERVER_INVALID:
349
"Zeroconf server invalid",
350
avahi.SERVER_REGISTERING: None,
351
avahi.SERVER_COLLISION:
352
"Zeroconf server name collision",
353
avahi.SERVER_FAILURE:
354
"Zeroconf server failure" }
355
if state in bad_states:
356
if bad_states[state] is not None:
358
logger.error(bad_states[state])
360
logger.error(bad_states[state] + ": %r", error)
362
elif state == avahi.SERVER_RUNNING:
366
logger.debug("Unknown state: %r", state)
368
logger.debug("Unknown state: %r: %r", state, error)
371
"""Derived from the Avahi example code"""
372
if self.server is None:
373
self.server = dbus.Interface(
374
self.bus.get_object(avahi.DBUS_NAME,
375
avahi.DBUS_PATH_SERVER,
376
follow_name_owner_changes=True),
377
avahi.DBUS_INTERFACE_SERVER)
378
self.server.connect_to_signal("StateChanged",
379
self.server_state_changed)
380
self.server_state_changed(self.server.GetState())
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))
150
group = dbus.Interface\
151
(bus.get_object(avahi.DBUS_NAME,
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
401
173
class Client(object):
402
174
"""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
176
name: string; from the config file, used in log messages
177
fingerprint: string (40 or 32 hexadecimal digits); used to
178
uniquely identify the client
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); object creation, not client host
182
last_checked_ok: datetime.datetime() or None if not yet checked OK
183
timeout: datetime.timedelta(); How long from last_checked_ok
184
until this client is invalid
185
interval: datetime.timedelta(); How often to start a new checker
186
stop_hook: If set, called by stop() as stop_hook(self)
187
checker: subprocess.Popen(); a running checker process used
188
to see if the client lives.
189
'None' if no process is running.
190
checker_initiator_tag: a gobject event source tag, or None
191
stop_initiator_tag: - '' -
192
checker_callback_tag: - '' -
193
checker_command: string; External command which is run to check if
194
client lives. %() expansions are done at
414
195
runtime with vars(self) as dict, so that for
415
196
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
198
_timeout: Real variable for 'timeout'
199
_interval: Real variable for 'interval'
200
_timeout_milliseconds: Used when calling gobject.timeout_add()
201
_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):
203
def _set_timeout(self, timeout):
204
"Setter function for 'timeout' attribute"
205
self._timeout = timeout
206
self._timeout_milliseconds = ((self.timeout.days
207
* 24 * 60 * 60 * 1000)
208
+ (self.timeout.seconds * 1000)
209
+ (self.timeout.microseconds
211
timeout = property(lambda self: self._timeout,
214
def _set_interval(self, interval):
215
"Setter function for 'interval' attribute"
216
self._interval = interval
217
self._interval_milliseconds = ((self.interval.days
218
* 24 * 60 * 60 * 1000)
219
+ (self.interval.seconds
221
+ (self.interval.microseconds
223
interval = property(lambda self: self._interval,
226
def __init__(self, name = None, stop_hook=None, config=None):
227
"""Note: the 'checker' key in 'config' sets the
228
'checker_command' attribute and *not* the 'checker'
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)
233
logger.debug(u"Creating client %r", self.name)
538
234
# Uppercase and remove spaces from fingerprint for later
539
235
# 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
237
self.fingerprint = config["fingerprint"].upper()\
239
logger.debug(u" Fingerprint: %s", self.fingerprint)
240
if "secret" in config:
241
self.secret = config["secret"].decode(u"base64")
242
elif "secfile" in config:
243
secfile = open(os.path.expanduser(os.path.expandvars
244
(config["secfile"])))
245
self.secret = secfile.read()
248
raise TypeError(u"No secret or secfile for client %s"
250
self.host = config.get("host", "")
251
self.created = datetime.datetime.now()
252
self.last_checked_ok = None
253
self.timeout = string_to_delta(config["timeout"])
254
self.interval = string_to_delta(config["interval"])
255
self.stop_hook = stop_hook
546
256
self.checker = None
547
257
self.checker_initiator_tag = None
548
self.disable_initiator_tag = None
258
self.stop_initiator_tag = None
549
259
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()
260
self.check_command = config["checker"]
574
262
"""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
263
# Schedule a new checker to be started an 'interval' from now,
609
264
# 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(),
265
self.checker_initiator_tag = gobject.timeout_add\
266
(self._interval_milliseconds,
621
268
# Also start a new checker *right now*.
622
269
self.start_checker()
624
def checker_callback(self, pid, condition, command):
270
# Schedule a stop() when 'timeout' has passed
271
self.stop_initiator_tag = gobject.timeout_add\
272
(self._timeout_milliseconds,
276
The possibility that a client might be restarted is left open,
277
but not currently used."""
278
# If this client doesn't have a secret, it is already stopped.
279
if hasattr(self, "secret") and self.secret:
280
logger.info(u"Stopping client %s", self.name)
284
if getattr(self, "stop_initiator_tag", False):
285
gobject.source_remove(self.stop_initiator_tag)
286
self.stop_initiator_tag = None
287
if getattr(self, "checker_initiator_tag", False):
288
gobject.source_remove(self.checker_initiator_tag)
289
self.checker_initiator_tag = None
293
# Do not run this again if called by a gobject.timeout_add
296
self.stop_hook = None
298
def checker_callback(self, pid, condition):
625
299
"""The checker has completed, so take appropriate actions."""
300
now = datetime.datetime.now()
626
301
self.checker_callback_tag = None
627
302
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?",
303
if os.WIFEXITED(condition) \
304
and (os.WEXITSTATUS(condition) == 0):
305
logger.info(u"Checker for %(name)s succeeded",
307
self.last_checked_ok = now
308
gobject.source_remove(self.stop_initiator_tag)
309
self.stop_initiator_tag = gobject.timeout_add\
310
(self._timeout_milliseconds,
312
elif not os.WIFEXITED(condition):
313
logger.warning(u"Checker for %(name)s crashed?",
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()
316
logger.info(u"Checker for %(name)s failed",
664
318
def start_checker(self):
665
319
"""Start a new checker subprocess if one is not running.
667
320
If a checker already exists, leave it running and do
669
322
# 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
323
# did that, then if a checker (for some reason) started
324
# running slowly and taking more than 'interval' time, the
325
# client would inevitably timeout, since no checker would get
326
# a chance to run to completion. If we instead leave running
674
327
# 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
328
# than 'timeout' for the client to be declared invalid, which
329
# is as it should be.
692
330
if self.checker is None:
694
# In case checker_command has exactly one % operator
695
command = self.checker_command % self.host
332
# In case check_command has exactly one % operator
333
command = self.check_command % self.host
696
334
except TypeError:
697
335
# Escape attributes for the shell
698
escaped_attrs = dict(
700
re.escape(unicode(str(getattr(self, attr, "")),
704
self.runtime_expansions)
336
escaped_attrs = dict((key, re.escape(str(val)))
338
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)
340
command = self.check_command % escaped_attrs
341
except TypeError, error:
342
logger.error(u'Could not format string "%s":'
343
u' %s', self.check_command, error)
711
344
return True # Try again later
712
self.current_checker_command = command
714
logger.info("Starting checker %r for %s",
346
logger.info(u"Starting checker %r for %s",
715
347
command, self.name)
716
348
# We don't need to redirect stdout and stderr, since
717
349
# in normal mode, that is already done by daemon(),
743
367
self.checker_callback_tag = None
744
368
if getattr(self, "checker", None) is None:
746
logger.debug("Stopping checker for %(name)s", vars(self))
370
logger.debug(u"Stopping checker for %(name)s", vars(self))
748
self.checker.terminate()
372
os.kill(self.checker.pid, signal.SIGTERM)
750
374
#if self.checker.poll() is None:
751
# self.checker.kill()
752
except OSError as error:
375
# os.kill(self.checker.pid, signal.SIGKILL)
376
except OSError, error:
753
377
if error.errno != errno.ESRCH: # No such process
755
379
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.
380
def still_valid(self):
381
"""Has the timeout not yet passed for this client?"""
382
now = datetime.datetime.now()
383
if self.last_checked_ok is None:
384
return now < (self.created + self.timeout)
386
return now < (self.last_checked_ok + self.timeout)
389
def peer_certificate(session):
390
"Return the peer's OpenPGP certificate as a bytestring"
391
# If not an OpenPGP certificate...
392
if gnutls.library.functions.gnutls_certificate_type_get\
393
(session._c_object) \
394
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
395
# ...do the normal thing
396
return session.peer_certificate
397
list_size = ctypes.c_uint()
398
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
399
(session._c_object, ctypes.byref(list_size))
400
if list_size.value == 0:
403
return ctypes.string_at(cert.data, cert.size)
406
def fingerprint(openpgp):
407
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
408
# New GnuTLS "datum" with the OpenPGP public key
409
datum = gnutls.library.types.gnutls_datum_t\
410
(ctypes.cast(ctypes.c_char_p(openpgp),
411
ctypes.POINTER(ctypes.c_ubyte)),
412
ctypes.c_uint(len(openpgp)))
413
# New empty GnuTLS certificate
414
crt = gnutls.library.types.gnutls_openpgp_crt_t()
415
gnutls.library.functions.gnutls_openpgp_crt_init\
417
# Import the OpenPGP public key into the certificate
418
gnutls.library.functions.gnutls_openpgp_crt_import\
419
(crt, ctypes.byref(datum),
420
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
421
# Verify the self signature in the key
422
crtverify = ctypes.c_uint()
423
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
424
(crt, 0, ctypes.byref(crtverify))
425
if crtverify.value != 0:
426
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
427
raise gnutls.errors.CertificateSecurityError("Verify failed")
428
# New buffer for the fingerprint
429
buf = ctypes.create_string_buffer(20)
430
buf_len = ctypes.c_size_t()
431
# Get the fingerprint from the certificate into the buffer
432
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
433
(crt, ctypes.byref(buf), ctypes.byref(buf_len))
434
# Deinit the certificate
435
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
436
# Convert the buffer to a Python bytestring
437
fpr = ctypes.string_at(buf, buf_len.value)
438
# Convert the bytestring to hexadecimal notation
439
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
443
class TCP_handler(SocketServer.BaseRequestHandler, object):
444
"""A TCP request handler class.
445
Instantiated by IPv6_TCPServer for each request to handle it.
1646
446
Note: This will run in its own forked process."""
1648
448
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
449
logger.info(u"TCP connection from: %s",
450
unicode(self.client_address))
451
session = gnutls.connection.ClientSession\
452
(self.request, gnutls.connection.X509Credentials())
454
line = self.request.makefile().readline()
455
logger.debug(u"Protocol version: %r", line)
457
if int(line.strip().split()[0]) > 1:
459
except (ValueError, IndexError, RuntimeError), error:
460
logger.error(u"Unknown protocol version: %s", error)
463
# Note: gnutls.connection.X509Credentials is really a generic
464
# GnuTLS certificate credentials object so long as no X.509
465
# keys are added to it. Therefore, we can use it here despite
466
# using OpenPGP certificates.
468
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
469
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
471
priority = "NORMAL" # Fallback default, since this
473
if self.server.settings["priority"]:
474
priority = self.server.settings["priority"]
475
gnutls.library.functions.gnutls_priority_set_direct\
476
(session._c_object, priority, None)
480
except gnutls.errors.GNUTLSError, error:
481
logger.warning(u"Handshake failed: %s", error)
482
# Do not run session.bye() here: the session is not
483
# established. Just abandon the request.
486
fpr = fingerprint(peer_certificate(session))
487
except (TypeError, gnutls.errors.GNUTLSError), error:
488
logger.warning(u"Bad certificate: %s", error)
491
logger.debug(u"Fingerprint: %s", fpr)
493
for c in self.server.clients:
494
if c.fingerprint == fpr:
498
logger.warning(u"Client not found for fingerprint: %s",
502
# Have to check if client.still_valid(), since it is possible
503
# that the client timed out while establishing the GnuTLS
505
if not client.still_valid():
506
logger.warning(u"Client %(name)s is invalid",
511
while sent_size < len(client.secret):
512
sent = session.send(client.secret[sent_size:])
513
logger.debug(u"Sent: %d, remaining: %d",
514
sent, len(client.secret)
515
- (sent_size + sent))
520
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
521
"""IPv6 TCP server. Accepts 'None' as address and/or port.
523
settings: Server settings
524
clients: Set() of Client objects
1904
525
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)
527
address_family = socket.AF_INET6
528
def __init__(self, *args, **kwargs):
529
if "settings" in kwargs:
530
self.settings = kwargs["settings"]
531
del kwargs["settings"]
532
if "clients" in kwargs:
533
self.clients = kwargs["clients"]
534
del kwargs["clients"]
536
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1915
537
def server_bind(self):
1916
538
"""This overrides the normal server_bind() function
1917
539
to bind to an interface if one was specified, and also NOT to
1918
540
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",
541
if self.settings["interface"]:
542
# 25 is from /usr/include/asm-i486/socket.h
543
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
545
self.socket.setsockopt(socket.SOL_SOCKET,
547
self.settings["interface"])
548
except socket.error, error:
549
if error[0] == errno.EPERM:
550
logger.error(u"No permission to"
551
u" bind to interface %s",
552
self.settings["interface"])
1945
555
# Only bind(2) the socket if we really need to.
1946
556
if self.server_address[0] or self.server_address[1]:
1947
557
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,
559
self.server_address = (in6addr_any,
1953
560
self.server_address[1])
1954
561
elif not self.server_address[1]:
1955
562
self.server_address = (self.server_address[0],
1957
# if self.interface:
564
# if self.settings["interface"]:
1958
565
# self.server_address = (self.server_address[0],
1961
568
# 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)
571
return super(IPv6_TCPServer, self).server_bind()
1989
572
def server_activate(self):
1990
573
if self.enabled:
1991
return socketserver.TCPServer.server_activate(self)
574
return super(IPv6_TCPServer, self).server_activate()
1993
575
def enable(self):
1994
576
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
579
def string_to_delta(interval):
2073
580
"""Parse a string and return a datetime.timedelta
2075
582
>>> string_to_delta('7d')
2076
583
datetime.timedelta(7)
2077
584
>>> string_to_delta('60s')
2191
721
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2192
722
"servicename": "Mandos",
2197
"statedir": "/var/lib/mandos"
2200
725
# Parse config file for server-global settings
2201
server_config = configparser.SafeConfigParser(server_defaults)
726
server_config = ConfigParser.SafeConfigParser(server_defaults)
2202
727
del server_defaults
2203
server_config.read(os.path.join(options.configdir,
728
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2205
729
# Convert the SafeConfigParser object to a dict
2206
730
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",
731
# Use getboolean on the boolean config option
732
server_settings["debug"] = server_config.getboolean\
2214
734
del server_config
2216
736
# Override the settings from the config file with command line
2217
737
# options, if set.
2218
738
for option in ("interface", "address", "port", "debug",
2219
"priority", "servicename", "configdir",
2220
"use_dbus", "use_ipv6", "debuglevel", "restore",
739
"priority", "servicename", "configdir"):
2222
740
value = getattr(options, option)
2223
741
if value is not None:
2224
742
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
744
# Now we have our good server settings in "server_settings"
2232
##################################################################
2235
746
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)
749
syslogger.setLevel(logging.WARNING)
750
console.setLevel(logging.WARNING)
2251
752
if server_settings["servicename"] != "Mandos":
2252
syslogger.setFormatter(logging.Formatter
2253
('Mandos ({0}) [%(process)d]:'
2254
' %(levelname)s: %(message)s'
2255
.format(server_settings
753
syslogger.setFormatter(logging.Formatter\
754
('Mandos (%s): %%(levelname)s:'
756
% server_settings["servicename"]))
2258
758
# Parse config file with clients
2259
client_config = configparser.SafeConfigParser(Client
759
client_defaults = { "timeout": "1h",
761
"checker": "fping -q -- %(host)s",
764
client_config = ConfigParser.SafeConfigParser(client_defaults)
2261
765
client_config.read(os.path.join(server_settings["configdir"],
2262
766
"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
769
tcp_server = IPv6_TCPServer((server_settings["address"],
770
server_settings["port"]),
772
settings=server_settings,
774
pidfilename = "/var/run/mandos.pid"
776
pidfile = open(pidfilename, "w")
777
except IOError, error:
778
logger.error("Could not open file %r", pidfilename)
783
uid = pwd.getpwnam("mandos").pw_uid
786
uid = pwd.getpwnam("nobody").pw_uid
790
gid = pwd.getpwnam("mandos").pw_gid
793
gid = pwd.getpwnam("nogroup").pw_gid
2297
except OSError as error:
799
except OSError, error:
2298
800
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()
804
service = AvahiService(name = server_settings["servicename"],
805
servicetype = "_mandos._tcp", )
806
if server_settings["interface"]:
807
service.interface = if_nametoindex\
808
(server_settings["interface"])
2328
810
global main_loop
2329
813
# From the Avahi example code
2330
DBusGMainLoop(set_as_default=True)
814
DBusGMainLoop(set_as_default=True )
2331
815
main_loop = gobject.MainLoop()
2332
816
bus = dbus.SystemBus()
817
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
818
avahi.DBUS_PATH_SERVER),
819
avahi.DBUS_INTERFACE_SERVER)
2333
820
# 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")
822
def remove_from_clients(client):
823
clients.remove(client)
825
logger.critical(u"No clients left, exiting")
828
clients.update(Set(Client(name = section,
829
stop_hook = remove_from_clients,
831
= dict(client_config.items(section)))
832
for section in client_config.sections()))
834
logger.critical(u"No clients defined")
838
# Redirect stdin so all checkers get /dev/null
839
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
840
os.dup2(null, sys.stdin.fileno())
845
logger.removeHandler(console)
846
# Close all input and output, do double fork, etc.
851
pidfile.write(str(pid) + "\n")
855
logger.error(u"Could not write to file %r with PID %d",
858
# "pidfile" was never created
863
"Cleanup function; run on exit"
865
# From the Avahi example code
866
if not group is None:
869
# End of Avahi example code
872
client = clients.pop()
873
client.stop_hook = None
876
atexit.register(cleanup)
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
879
signal.signal(signal.SIGINT, signal.SIG_IGN)
2474
880
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2475
881
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()
883
for client in clients:
2623
886
tcp_server.enable()
2624
887
tcp_server.server_activate()
2626
889
# Find out what port we got
2627
890
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())
891
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
892
u" scope_id %d" % tcp_server.socket.getsockname())
2636
894
#service.interface = tcp_server.socket.getsockname()[3]
2639
897
# From the Avahi example code
898
server.connect_to_signal("StateChanged", server_state_changed)
2642
except dbus.exceptions.DBusException as error:
2643
logger.critical("D-Bus Exception", exc_info=error)
900
server_state_changed(server.GetState())
901
except dbus.exceptions.DBusException, error:
902
logger.critical(u"DBusException: %s", error)
2646
904
# End of Avahi example code
2648
906
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2649
907
lambda *args, **kwargs:
2650
(tcp_server.handle_request
2651
(*args[2:], **kwargs) or True))
908
tcp_server.handle_request\
909
(*args[2:], **kwargs) or True)
2653
logger.debug("Starting main loop")
911
logger.debug(u"Starting main loop")
2655
except AvahiError as error:
2656
logger.critical("Avahi Error", exc_info=error)
913
except AvahiError, error:
914
logger.critical(u"AvahiError: %s" + unicode(error))
2659
916
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
920
if __name__ == '__main__':