241
106
max_renames: integer; maximum number of renames
242
107
rename_count: integer; counter so we only rename after collisions
243
108
a sensible number of times
244
group: D-Bus Entry Group
246
bus: dbus.SystemBus()
249
110
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
250
servicetype = None, port = None, TXT = None,
251
domain = "", host = "", max_renames = 32768,
252
protocol = avahi.PROTO_UNSPEC, bus = None):
111
type = None, port = None, TXT = None, domain = "",
112
host = "", max_renames = 32768):
253
113
self.interface = interface
255
self.type = servicetype
257
self.TXT = TXT if TXT is not None else []
258
121
self.domain = domain
260
123
self.rename_count = 0
261
124
self.max_renames = max_renames
262
self.protocol = protocol
263
self.group = None # our entry group
266
self.entry_group_state_changed_match = None
268
125
def rename(self):
269
126
"""Derived from the Avahi example code"""
270
127
if self.rename_count >= self.max_renames:
271
logger.critical("No suitable Zeroconf service name found"
272
" after %i retries, exiting.",
128
logger.critical(u"No suitable Zeroconf service name found"
129
u" after %i retries, exiting.",
274
131
raise AvahiServiceError("Too many renames")
275
self.name = unicode(self.server
276
.GetAlternativeServiceName(self.name))
277
logger.info("Changing Zeroconf service name to %r ...",
132
self.name = server.GetAlternativeServiceName(self.name)
133
logger.info(u"Changing Zeroconf service name to %r ...",
135
syslogger.setFormatter(logging.Formatter\
136
('Mandos (%s): %%(levelname)s:'
137
' %%(message)s' % self.name))
282
except dbus.exceptions.DBusException as error:
283
logger.critical("D-Bus Exception", exc_info=error)
286
140
self.rename_count += 1
288
141
def remove(self):
289
142
"""Derived from the Avahi example code"""
290
if self.entry_group_state_changed_match is not None:
291
self.entry_group_state_changed_match.remove()
292
self.entry_group_state_changed_match = None
293
if self.group is not None:
143
if group is not None:
297
146
"""Derived from the Avahi example code"""
299
if self.group is None:
300
self.group = dbus.Interface(
301
self.bus.get_object(avahi.DBUS_NAME,
302
self.server.EntryGroupNew()),
303
avahi.DBUS_INTERFACE_ENTRY_GROUP)
304
self.entry_group_state_changed_match = (
305
self.group.connect_to_signal(
306
'StateChanged', self.entry_group_state_changed))
307
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
308
self.name, self.type)
309
self.group.AddService(
312
dbus.UInt32(0), # flags
313
self.name, self.type,
314
self.domain, self.host,
315
dbus.UInt16(self.port),
316
avahi.string_array_to_txt_array(self.TXT))
319
def entry_group_state_changed(self, state, error):
320
"""Derived from the Avahi example code"""
321
logger.debug("Avahi entry group state change: %i", state)
323
if state == avahi.ENTRY_GROUP_ESTABLISHED:
324
logger.debug("Zeroconf service established.")
325
elif state == avahi.ENTRY_GROUP_COLLISION:
326
logger.info("Zeroconf service name collision.")
328
elif state == avahi.ENTRY_GROUP_FAILURE:
329
logger.critical("Avahi: Error in group state changed %s",
331
raise AvahiGroupError("State changed: {0!s}"
335
"""Derived from the Avahi example code"""
336
if self.group is not None:
339
except (dbus.exceptions.UnknownMethodException,
340
dbus.exceptions.DBusException):
345
def server_state_changed(self, state, error=None):
346
"""Derived from the Avahi example code"""
347
logger.debug("Avahi server state change: %i", state)
348
bad_states = { avahi.SERVER_INVALID:
349
"Zeroconf server invalid",
350
avahi.SERVER_REGISTERING: None,
351
avahi.SERVER_COLLISION:
352
"Zeroconf server name collision",
353
avahi.SERVER_FAILURE:
354
"Zeroconf server failure" }
355
if state in bad_states:
356
if bad_states[state] is not None:
358
logger.error(bad_states[state])
360
logger.error(bad_states[state] + ": %r", error)
362
elif state == avahi.SERVER_RUNNING:
366
logger.debug("Unknown state: %r", state)
368
logger.debug("Unknown state: %r: %r", state, error)
371
"""Derived from the Avahi example code"""
372
if self.server is None:
373
self.server = dbus.Interface(
374
self.bus.get_object(avahi.DBUS_NAME,
375
avahi.DBUS_PATH_SERVER,
376
follow_name_owner_changes=True),
377
avahi.DBUS_INTERFACE_SERVER)
378
self.server.connect_to_signal("StateChanged",
379
self.server_state_changed)
380
self.server_state_changed(self.server.GetState())
383
class AvahiServiceToSyslog(AvahiService):
385
"""Add the new name to the syslog messages"""
386
ret = AvahiService.rename(self)
387
syslogger.setFormatter(logging.Formatter
388
('Mandos ({0}) [%(process)d]:'
389
' %(levelname)s: %(message)s'
394
def timedelta_to_milliseconds(td):
395
"Convert a datetime.timedelta() to milliseconds"
396
return ((td.days * 24 * 60 * 60 * 1000)
397
+ (td.seconds * 1000)
398
+ (td.microseconds // 1000))
149
group = dbus.Interface\
150
(bus.get_object(avahi.DBUS_NAME,
151
server.EntryGroupNew()),
152
avahi.DBUS_INTERFACE_ENTRY_GROUP)
153
group.connect_to_signal('StateChanged',
154
entry_group_state_changed)
155
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
156
service.name, service.type)
158
self.interface, # interface
159
avahi.PROTO_INET6, # protocol
160
dbus.UInt32(0), # flags
161
self.name, self.type,
162
self.domain, self.host,
163
dbus.UInt16(self.port),
164
avahi.string_array_to_txt_array(self.TXT))
167
# From the Avahi example code:
168
group = None # our entry group
169
# End of Avahi example code
401
172
class Client(object):
402
173
"""A representation of a client host served by this server.
405
approved: bool(); 'None' if not yet approved/disapproved
406
approval_delay: datetime.timedelta(); Time to wait for approval
407
approval_duration: datetime.timedelta(); Duration of one approval
408
checker: subprocess.Popen(); a running checker process used
409
to see if the client lives.
410
'None' if no process is running.
411
checker_callback_tag: a gobject event source tag, or None
412
checker_command: string; External command which is run to check
413
if client lives. %() expansions are done at
175
name: string; from the config file, used in log messages
176
fingerprint: string (40 or 32 hexadecimal digits); used to
177
uniquely identify the client
178
secret: bytestring; sent verbatim (over TLS) to client
179
host: string; available for use by the checker command
180
created: datetime.datetime(); object creation, not client host
181
last_checked_ok: datetime.datetime() or None if not yet checked OK
182
timeout: datetime.timedelta(); How long from last_checked_ok
183
until this client is invalid
184
interval: datetime.timedelta(); How often to start a new checker
185
stop_hook: If set, called by stop() as stop_hook(self)
186
checker: subprocess.Popen(); a running checker process used
187
to see if the client lives.
188
'None' if no process is running.
189
checker_initiator_tag: a gobject event source tag, or None
190
stop_initiator_tag: - '' -
191
checker_callback_tag: - '' -
192
checker_command: string; External command which is run to check if
193
client lives. %() expansions are done at
414
194
runtime with vars(self) as dict, so that for
415
195
instance %(name)s can be used in the command.
416
checker_initiator_tag: a gobject event source tag, or None
417
created: datetime.datetime(); (UTC) object creation
418
client_structure: Object describing what attributes a client has
419
and is used for storing the client at exit
420
current_checker_command: string; current running checker_command
421
disable_initiator_tag: a gobject event source tag, or None
423
fingerprint: string (40 or 32 hexadecimal digits); used to
424
uniquely identify the client
425
host: string; available for use by the checker command
426
interval: datetime.timedelta(); How often to start a new checker
427
last_approval_request: datetime.datetime(); (UTC) or None
428
last_checked_ok: datetime.datetime(); (UTC) or None
429
last_checker_status: integer between 0 and 255 reflecting exit
430
status of last checker. -1 reflects crashed
431
checker, -2 means no checker completed yet.
432
last_enabled: datetime.datetime(); (UTC) or None
433
name: string; from the config file, used in log messages and
435
secret: bytestring; sent verbatim (over TLS) to client
436
timeout: datetime.timedelta(); How long from last_checked_ok
437
until this client is disabled
438
extended_timeout: extra long timeout when secret has been sent
439
runtime_expansions: Allowed attributes for runtime expansion.
440
expires: datetime.datetime(); time (UTC) when a client will be
197
_timeout: Real variable for 'timeout'
198
_interval: Real variable for 'interval'
199
_timeout_milliseconds: Used when calling gobject.timeout_add()
200
_interval_milliseconds: - '' -
444
runtime_expansions = ("approval_delay", "approval_duration",
445
"created", "enabled", "fingerprint",
446
"host", "interval", "last_checked_ok",
447
"last_enabled", "name", "timeout")
448
client_defaults = { "timeout": "5m",
449
"extended_timeout": "15m",
451
"checker": "fping -q -- %%(host)s",
453
"approval_delay": "0s",
454
"approval_duration": "1s",
455
"approved_by_default": "True",
459
def timeout_milliseconds(self):
460
"Return the 'timeout' attribute in milliseconds"
461
return timedelta_to_milliseconds(self.timeout)
463
def extended_timeout_milliseconds(self):
464
"Return the 'extended_timeout' attribute in milliseconds"
465
return timedelta_to_milliseconds(self.extended_timeout)
467
def interval_milliseconds(self):
468
"Return the 'interval' attribute in milliseconds"
469
return timedelta_to_milliseconds(self.interval)
471
def approval_delay_milliseconds(self):
472
return timedelta_to_milliseconds(self.approval_delay)
475
def config_parser(config):
476
"""Construct a new dict of client settings of this form:
477
{ client_name: {setting_name: value, ...}, ...}
478
with exceptions for any special settings as defined above.
479
NOTE: Must be a pure function. Must return the same result
480
value given the same arguments.
483
for client_name in config.sections():
484
section = dict(config.items(client_name))
485
client = settings[client_name] = {}
487
client["host"] = section["host"]
488
# Reformat values from string types to Python types
489
client["approved_by_default"] = config.getboolean(
490
client_name, "approved_by_default")
491
client["enabled"] = config.getboolean(client_name,
494
client["fingerprint"] = (section["fingerprint"].upper()
496
if "secret" in section:
497
client["secret"] = section["secret"].decode("base64")
498
elif "secfile" in section:
499
with open(os.path.expanduser(os.path.expandvars
500
(section["secfile"])),
502
client["secret"] = secfile.read()
504
raise TypeError("No secret or secfile for section {0}"
506
client["timeout"] = string_to_delta(section["timeout"])
507
client["extended_timeout"] = string_to_delta(
508
section["extended_timeout"])
509
client["interval"] = string_to_delta(section["interval"])
510
client["approval_delay"] = string_to_delta(
511
section["approval_delay"])
512
client["approval_duration"] = string_to_delta(
513
section["approval_duration"])
514
client["checker_command"] = section["checker"]
515
client["last_approval_request"] = None
516
client["last_checked_ok"] = None
517
client["last_checker_status"] = -2
521
def __init__(self, settings, name = None):
202
def _set_timeout(self, timeout):
203
"Setter function for 'timeout' attribute"
204
self._timeout = timeout
205
self._timeout_milliseconds = ((self.timeout.days
206
* 24 * 60 * 60 * 1000)
207
+ (self.timeout.seconds * 1000)
208
+ (self.timeout.microseconds
210
timeout = property(lambda self: self._timeout,
213
def _set_interval(self, interval):
214
"Setter function for 'interval' attribute"
215
self._interval = interval
216
self._interval_milliseconds = ((self.interval.days
217
* 24 * 60 * 60 * 1000)
218
+ (self.interval.seconds
220
+ (self.interval.microseconds
222
interval = property(lambda self: self._interval,
225
def __init__(self, name = None, stop_hook=None, config={}):
226
"""Note: the 'checker' key in 'config' sets the
227
'checker_command' attribute and *not* the 'checker'
523
# adding all client settings
524
for setting, value in settings.iteritems():
525
setattr(self, setting, value)
528
if not hasattr(self, "last_enabled"):
529
self.last_enabled = datetime.datetime.utcnow()
530
if not hasattr(self, "expires"):
531
self.expires = (datetime.datetime.utcnow()
534
self.last_enabled = None
537
logger.debug("Creating client %r", self.name)
230
logger.debug(u"Creating client %r", self.name)
538
231
# Uppercase and remove spaces from fingerprint for later
539
232
# comparison purposes with return value from the fingerprint()
541
logger.debug(" Fingerprint: %s", self.fingerprint)
542
self.created = settings.get("created",
543
datetime.datetime.utcnow())
545
# attributes specific for this server instance
234
self.fingerprint = config["fingerprint"].upper()\
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
237
if "secret" in config:
238
self.secret = config["secret"].decode(u"base64")
239
elif "secfile" in config:
240
sf = open(config["secfile"])
241
self.secret = sf.read()
244
raise TypeError(u"No secret or secfile for client %s"
246
self.host = config.get("host", "")
247
self.created = datetime.datetime.now()
248
self.last_checked_ok = None
249
self.timeout = string_to_delta(config["timeout"])
250
self.interval = string_to_delta(config["interval"])
251
self.stop_hook = stop_hook
546
252
self.checker = None
547
253
self.checker_initiator_tag = None
548
self.disable_initiator_tag = None
254
self.stop_initiator_tag = None
549
255
self.checker_callback_tag = None
550
self.current_checker_command = None
552
self.approvals_pending = 0
553
self.changedstate = (multiprocessing_manager
554
.Condition(multiprocessing_manager
556
self.client_structure = [attr for attr in
557
self.__dict__.iterkeys()
558
if not attr.startswith("_")]
559
self.client_structure.append("client_structure")
561
for name, t in inspect.getmembers(type(self),
565
if not name.startswith("_"):
566
self.client_structure.append(name)
568
# Send notice to process children that client state has changed
569
def send_changedstate(self):
570
with self.changedstate:
571
self.changedstate.notify_all()
256
self.check_command = config["checker"]
574
258
"""Start this client's checker and timeout hooks"""
575
if getattr(self, "enabled", False):
578
self.expires = datetime.datetime.utcnow() + self.timeout
580
self.last_enabled = datetime.datetime.utcnow()
582
self.send_changedstate()
584
def disable(self, quiet=True):
585
"""Disable this client."""
586
if not getattr(self, "enabled", False):
589
logger.info("Disabling client %s", self.name)
590
if getattr(self, "disable_initiator_tag", None) is not None:
591
gobject.source_remove(self.disable_initiator_tag)
592
self.disable_initiator_tag = None
594
if getattr(self, "checker_initiator_tag", None) is not None:
595
gobject.source_remove(self.checker_initiator_tag)
596
self.checker_initiator_tag = None
600
self.send_changedstate()
601
# Do not run this again if called by a gobject.timeout_add
607
def init_checker(self):
608
259
# Schedule a new checker to be started an 'interval' from now,
609
260
# and every interval from then on.
610
if self.checker_initiator_tag is not None:
611
gobject.source_remove(self.checker_initiator_tag)
612
self.checker_initiator_tag = (gobject.timeout_add
613
(self.interval_milliseconds(),
615
# Schedule a disable() when 'timeout' has passed
616
if self.disable_initiator_tag is not None:
617
gobject.source_remove(self.disable_initiator_tag)
618
self.disable_initiator_tag = (gobject.timeout_add
619
(self.timeout_milliseconds(),
261
self.checker_initiator_tag = gobject.timeout_add\
262
(self._interval_milliseconds,
621
264
# Also start a new checker *right now*.
622
265
self.start_checker()
624
def checker_callback(self, pid, condition, command):
266
# Schedule a stop() when 'timeout' has passed
267
self.stop_initiator_tag = gobject.timeout_add\
268
(self._timeout_milliseconds,
272
The possibility that a client might be restarted is left open,
273
but not currently used."""
274
# If this client doesn't have a secret, it is already stopped.
275
if hasattr(self, "secret") and self.secret:
276
logger.info(u"Stopping client %s", self.name)
280
if getattr(self, "stop_initiator_tag", False):
281
gobject.source_remove(self.stop_initiator_tag)
282
self.stop_initiator_tag = None
283
if getattr(self, "checker_initiator_tag", False):
284
gobject.source_remove(self.checker_initiator_tag)
285
self.checker_initiator_tag = None
289
# Do not run this again if called by a gobject.timeout_add
292
self.stop_hook = None
294
def checker_callback(self, pid, condition):
625
295
"""The checker has completed, so take appropriate actions."""
296
now = datetime.datetime.now()
626
297
self.checker_callback_tag = None
627
298
self.checker = None
628
if os.WIFEXITED(condition):
629
self.last_checker_status = os.WEXITSTATUS(condition)
630
if self.last_checker_status == 0:
631
logger.info("Checker for %(name)s succeeded",
635
logger.info("Checker for %(name)s failed",
638
self.last_checker_status = -1
639
logger.warning("Checker for %(name)s crashed?",
299
if os.WIFEXITED(condition) \
300
and (os.WEXITSTATUS(condition) == 0):
301
logger.info(u"Checker for %(name)s succeeded",
303
self.last_checked_ok = now
304
gobject.source_remove(self.stop_initiator_tag)
305
self.stop_initiator_tag = gobject.timeout_add\
306
(self._timeout_milliseconds,
308
elif not os.WIFEXITED(condition):
309
logger.warning(u"Checker for %(name)s crashed?",
642
def checked_ok(self):
643
"""Assert that the client has been seen, alive and well."""
644
self.last_checked_ok = datetime.datetime.utcnow()
645
self.last_checker_status = 0
648
def bump_timeout(self, timeout=None):
649
"""Bump up the timeout for this client."""
651
timeout = self.timeout
652
if self.disable_initiator_tag is not None:
653
gobject.source_remove(self.disable_initiator_tag)
654
self.disable_initiator_tag = None
655
if getattr(self, "enabled", False):
656
self.disable_initiator_tag = (gobject.timeout_add
657
(timedelta_to_milliseconds
658
(timeout), self.disable))
659
self.expires = datetime.datetime.utcnow() + timeout
661
def need_approval(self):
662
self.last_approval_request = datetime.datetime.utcnow()
312
logger.info(u"Checker for %(name)s failed",
664
314
def start_checker(self):
665
315
"""Start a new checker subprocess if one is not running.
667
316
If a checker already exists, leave it running and do
669
318
# The reason for not killing a running checker is that if we
670
# did that, and if a checker (for some reason) started running
671
# slowly and taking more than 'interval' time, then the client
672
# would inevitably timeout, since no checker would get a
673
# chance to run to completion. If we instead leave running
319
# did that, then if a checker (for some reason) started
320
# running slowly and taking more than 'interval' time, the
321
# client would inevitably timeout, since no checker would get
322
# a chance to run to completion. If we instead leave running
674
323
# checkers alone, the checker would have to take more time
675
# than 'timeout' for the client to be disabled, which is as it
678
# If a checker exists, make sure it is not a zombie
680
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
681
except (AttributeError, OSError) as error:
682
if (isinstance(error, OSError)
683
and error.errno != errno.ECHILD):
687
logger.warning("Checker was a zombie")
688
gobject.source_remove(self.checker_callback_tag)
689
self.checker_callback(pid, status,
690
self.current_checker_command)
691
# Start a new checker if needed
324
# than 'timeout' for the client to be declared invalid, which
325
# is as it should be.
692
326
if self.checker is None:
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",
328
# In case check_command has exactly one % operator
329
command = self.check_command % self.host
331
# Escape attributes for the shell
332
escaped_attrs = dict((key, re.escape(str(val)))
334
vars(self).iteritems())
336
command = self.check_command % escaped_attrs
337
except TypeError, error:
338
logger.error(u'Could not format string "%s":'
339
u' %s', self.check_command, error)
340
return True # Try again later
342
logger.info(u"Starting checker %r for %s",
707
343
command, self.name)
708
344
# We don't need to redirect stdout and stderr, since
709
345
# in normal mode, that is already done by daemon(),
735
363
self.checker_callback_tag = None
736
364
if getattr(self, "checker", None) is None:
738
logger.debug("Stopping checker for %(name)s", vars(self))
366
logger.debug(u"Stopping checker for %(name)s", vars(self))
740
self.checker.terminate()
368
os.kill(self.checker.pid, signal.SIGTERM)
742
370
#if self.checker.poll() is None:
743
# self.checker.kill()
744
except OSError as error:
371
# os.kill(self.checker.pid, signal.SIGKILL)
372
except OSError, error:
745
373
if error.errno != errno.ESRCH: # No such process
747
375
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.
376
def still_valid(self):
377
"""Has the timeout not yet passed for this client?"""
378
now = datetime.datetime.now()
379
if self.last_checked_ok is None:
380
return now < (self.created + self.timeout)
382
return now < (self.last_checked_ok + self.timeout)
385
def peer_certificate(session):
386
"Return the peer's OpenPGP certificate as a bytestring"
387
# If not an OpenPGP certificate...
388
if gnutls.library.functions.gnutls_certificate_type_get\
389
(session._c_object) \
390
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
391
# ...do the normal thing
392
return session.peer_certificate
393
list_size = ctypes.c_uint()
394
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
395
(session._c_object, ctypes.byref(list_size))
396
if list_size.value == 0:
399
return ctypes.string_at(cert.data, cert.size)
402
def fingerprint(openpgp):
403
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
404
# New GnuTLS "datum" with the OpenPGP public key
405
datum = gnutls.library.types.gnutls_datum_t\
406
(ctypes.cast(ctypes.c_char_p(openpgp),
407
ctypes.POINTER(ctypes.c_ubyte)),
408
ctypes.c_uint(len(openpgp)))
409
# New empty GnuTLS certificate
410
crt = gnutls.library.types.gnutls_openpgp_crt_t()
411
gnutls.library.functions.gnutls_openpgp_crt_init\
413
# Import the OpenPGP public key into the certificate
414
gnutls.library.functions.gnutls_openpgp_crt_import\
415
(crt, ctypes.byref(datum),
416
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
417
# Verify the self signature in the key
418
crtverify = ctypes.c_uint();
419
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
420
(crt, 0, ctypes.byref(crtverify))
421
if crtverify.value != 0:
422
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
423
raise gnutls.errors.CertificateSecurityError("Verify failed")
424
# New buffer for the fingerprint
425
buffer = ctypes.create_string_buffer(20)
426
buffer_length = ctypes.c_size_t()
427
# Get the fingerprint from the certificate into the buffer
428
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
429
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
430
# Deinit the certificate
431
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
432
# Convert the buffer to a Python bytestring
433
fpr = ctypes.string_at(buffer, buffer_length.value)
434
# Convert the bytestring to hexadecimal notation
435
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
439
class tcp_handler(SocketServer.BaseRequestHandler, object):
440
"""A TCP request handler class.
441
Instantiated by IPv6_TCPServer for each request to handle it.
1638
442
Note: This will run in its own forked process."""
1640
444
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
445
logger.info(u"TCP connection from: %s",
446
unicode(self.client_address))
447
session = gnutls.connection.ClientSession\
448
(self.request, gnutls.connection.X509Credentials())
450
line = self.request.makefile().readline()
451
logger.debug(u"Protocol version: %r", line)
453
if int(line.strip().split()[0]) > 1:
455
except (ValueError, IndexError, RuntimeError), error:
456
logger.error(u"Unknown protocol version: %s", error)
459
# Note: gnutls.connection.X509Credentials is really a generic
460
# GnuTLS certificate credentials object so long as no X.509
461
# keys are added to it. Therefore, we can use it here despite
462
# using OpenPGP certificates.
464
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
465
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
467
priority = "NORMAL" # Fallback default, since this
469
if self.server.settings["priority"]:
470
priority = self.server.settings["priority"]
471
gnutls.library.functions.gnutls_priority_set_direct\
472
(session._c_object, priority, None);
476
except gnutls.errors.GNUTLSError, error:
477
logger.warning(u"Handshake failed: %s", error)
478
# Do not run session.bye() here: the session is not
479
# established. Just abandon the request.
482
fpr = fingerprint(peer_certificate(session))
483
except (TypeError, gnutls.errors.GNUTLSError), error:
484
logger.warning(u"Bad certificate: %s", error)
487
logger.debug(u"Fingerprint: %s", fpr)
489
for c in self.server.clients:
490
if c.fingerprint == fpr:
494
logger.warning(u"Client not found for fingerprint: %s",
498
# Have to check if client.still_valid(), since it is possible
499
# that the client timed out while establishing the GnuTLS
501
if not client.still_valid():
502
logger.warning(u"Client %(name)s is invalid",
507
while sent_size < len(client.secret):
508
sent = session.send(client.secret[sent_size:])
509
logger.debug(u"Sent: %d, remaining: %d",
510
sent, len(client.secret)
511
- (sent_size + sent))
516
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
517
"""IPv6 TCP server. Accepts 'None' as address and/or port.
519
settings: Server settings
520
clients: Set() of Client objects
1896
521
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
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)
523
address_family = socket.AF_INET6
524
def __init__(self, *args, **kwargs):
525
if "settings" in kwargs:
526
self.settings = kwargs["settings"]
527
del kwargs["settings"]
528
if "clients" in kwargs:
529
self.clients = kwargs["clients"]
530
del kwargs["clients"]
532
return super(type(self), self).__init__(*args, **kwargs)
1907
533
def server_bind(self):
1908
534
"""This overrides the normal server_bind() function
1909
535
to bind to an interface if one was specified, and also NOT to
1910
536
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",
537
if self.settings["interface"]:
538
# 25 is from /usr/include/asm-i486/socket.h
539
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
541
self.socket.setsockopt(socket.SOL_SOCKET,
543
self.settings["interface"])
544
except socket.error, error:
545
if error[0] == errno.EPERM:
546
logger.error(u"No permission to"
547
u" bind to interface %s",
548
self.settings["interface"])
1937
551
# Only bind(2) the socket if we really need to.
1938
552
if self.server_address[0] or self.server_address[1]:
1939
553
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,
555
self.server_address = (in6addr_any,
1945
556
self.server_address[1])
1946
557
elif not self.server_address[1]:
1947
558
self.server_address = (self.server_address[0],
1949
# if self.interface:
560
# if self.settings["interface"]:
1950
561
# self.server_address = (self.server_address[0],
1953
564
# 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)
567
return super(type(self), self).server_bind()
1981
568
def server_activate(self):
1982
569
if self.enabled:
1983
return socketserver.TCPServer.server_activate(self)
570
return super(type(self), self).server_activate()
1985
571
def enable(self):
1986
572
self.enabled = True
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)
2064
575
def string_to_delta(interval):
2065
576
"""Parse a string and return a datetime.timedelta
2067
578
>>> string_to_delta('7d')
2068
579
datetime.timedelta(7)
2069
580
>>> string_to_delta('60s')
2183
722
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2184
723
"servicename": "Mandos",
2189
"statedir": "/var/lib/mandos"
2192
726
# Parse config file for server-global settings
2193
server_config = configparser.SafeConfigParser(server_defaults)
727
server_config = ConfigParser.SafeConfigParser(server_defaults)
2194
728
del server_defaults
2195
server_config.read(os.path.join(options.configdir,
729
server_config.read(os.path.join(options.configdir, "mandos.conf"))
2197
730
# Convert the SafeConfigParser object to a dict
2198
731
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",
732
# Use getboolean on the boolean config option
733
server_settings["debug"] = server_config.getboolean\
2206
735
del server_config
2208
737
# Override the settings from the config file with command line
2209
738
# options, if set.
2210
739
for option in ("interface", "address", "port", "debug",
2211
"priority", "servicename", "configdir",
2212
"use_dbus", "use_ipv6", "debuglevel", "restore",
740
"priority", "servicename", "configdir"):
2214
741
value = getattr(options, option)
2215
742
if value is not None:
2216
743
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
745
# Now we have our good server settings in "server_settings"
2224
##################################################################
2227
747
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)
750
syslogger.setLevel(logging.WARNING)
751
console.setLevel(logging.WARNING)
2243
753
if server_settings["servicename"] != "Mandos":
2244
syslogger.setFormatter(logging.Formatter
2245
('Mandos ({0}) [%(process)d]:'
2246
' %(levelname)s: %(message)s'
2247
.format(server_settings
754
syslogger.setFormatter(logging.Formatter\
755
('Mandos (%s): %%(levelname)s:'
757
% server_settings["servicename"]))
2250
759
# Parse config file with clients
2251
client_config = configparser.SafeConfigParser(Client
760
client_defaults = { "timeout": "1h",
762
"checker": "fping -q -- %(host)s",
765
client_config = ConfigParser.SafeConfigParser(client_defaults)
2253
766
client_config.read(os.path.join(server_settings["configdir"],
2254
767
"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
770
tcp_server = IPv6_TCPServer((server_settings["address"],
771
server_settings["port"]),
773
settings=server_settings,
775
pidfilename = "/var/run/mandos.pid"
777
pidfile = open(pidfilename, "w")
778
except IOError, error:
779
logger.error("Could not open file %r", pidfilename)
784
uid = pwd.getpwnam("mandos").pw_uid
787
uid = pwd.getpwnam("nobody").pw_uid
791
gid = pwd.getpwnam("mandos").pw_gid
794
gid = pwd.getpwnam("nogroup").pw_gid
2289
except OSError as error:
2290
if error.errno != errno.EPERM:
800
except OSError, error:
801
if error[0] != 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()
805
service = AvahiService(name = server_settings["servicename"],
806
type = "_mandos._tcp", );
807
if server_settings["interface"]:
808
service.interface = if_nametoindex\
809
(server_settings["interface"])
2320
811
global main_loop
2321
814
# From the Avahi example code
2322
DBusGMainLoop(set_as_default=True)
815
DBusGMainLoop(set_as_default=True )
2323
816
main_loop = gobject.MainLoop()
2324
817
bus = dbus.SystemBus()
818
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
819
avahi.DBUS_PATH_SERVER),
820
avahi.DBUS_INTERFACE_SERVER)
2325
821
# 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")
823
def remove_from_clients(client):
824
clients.remove(client)
826
logger.critical(u"No clients left, exiting")
829
clients.update(Set(Client(name = section,
830
stop_hook = remove_from_clients,
832
= dict(client_config.items(section)))
833
for section in client_config.sections()))
835
logger.critical(u"No clients defined")
839
# Redirect stdin so all checkers get /dev/null
840
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
841
os.dup2(null, sys.stdin.fileno())
846
logger.removeHandler(console)
847
# Close all input and output, do double fork, etc.
852
pidfile.write(str(pid) + "\n")
856
logger.error(u"Could not write to file %r with PID %d",
859
# "pidfile" was never created
864
"Cleanup function; run on exit"
866
# From the Avahi example code
867
if not group is None:
870
# End of Avahi example code
873
client = clients.pop()
874
client.stop_hook = None
877
atexit.register(cleanup)
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
2464
880
signal.signal(signal.SIGINT, signal.SIG_IGN)
2466
881
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2467
882
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()
884
for client in clients:
2615
887
tcp_server.enable()
2616
888
tcp_server.server_activate()
2618
890
# Find out what port we got
2619
891
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())
892
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
893
u" scope_id %d" % tcp_server.socket.getsockname())
2628
895
#service.interface = tcp_server.socket.getsockname()[3]
2631
898
# From the Avahi example code
899
server.connect_to_signal("StateChanged", server_state_changed)
2634
except dbus.exceptions.DBusException as error:
2635
logger.critical("D-Bus Exception", exc_info=error)
901
server_state_changed(server.GetState())
902
except dbus.exceptions.DBusException, error:
903
logger.critical(u"DBusException: %s", error)
2638
905
# End of Avahi example code
2640
907
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2641
908
lambda *args, **kwargs:
2642
(tcp_server.handle_request
2643
(*args[2:], **kwargs) or True))
909
tcp_server.handle_request\
910
(*args[2:], **kwargs) or True)
2645
logger.debug("Starting main loop")
912
logger.debug(u"Starting main loop")
913
main_loop_started = True
2647
except AvahiError as error:
2648
logger.critical("Avahi Error", exc_info=error)
915
except AvahiError, error:
916
logger.critical(u"AvahiError: %s" + unicode(error))
2651
918
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
922
if __name__ == '__main__':