258
125
self.rename_count = 0
259
126
self.max_renames = max_renames
260
self.protocol = protocol
261
self.group = None # our entry group
264
self.entry_group_state_changed_match = None
266
127
def rename(self):
267
128
"""Derived from the Avahi example code"""
268
129
if self.rename_count >= self.max_renames:
269
logger.critical("No suitable Zeroconf service name found"
270
" after %i retries, exiting.",
130
logger.critical(u"No suitable Zeroconf service name found"
131
u" after %i retries, exiting.",
271
132
self.rename_count)
272
raise AvahiServiceError("Too many renames")
273
self.name = unicode(self.server
274
.GetAlternativeServiceName(self.name))
275
logger.info("Changing Zeroconf service name to %r ...",
133
raise AvahiServiceError(u"Too many renames")
134
self.name = server.GetAlternativeServiceName(self.name)
135
logger.info(u"Changing Zeroconf service name to %r ...",
137
syslogger.setFormatter(logging.Formatter
138
('Mandos (%s): %%(levelname)s:'
139
' %%(message)s' % self.name))
280
except dbus.exceptions.DBusException as error:
281
logger.critical("D-Bus Exception", exc_info=error)
284
142
self.rename_count += 1
286
143
def remove(self):
287
144
"""Derived from the Avahi example code"""
288
if self.entry_group_state_changed_match is not None:
289
self.entry_group_state_changed_match.remove()
290
self.entry_group_state_changed_match = None
291
if self.group is not None:
145
if group is not None:
295
148
"""Derived from the Avahi example code"""
297
if self.group is None:
298
self.group = dbus.Interface(
299
self.bus.get_object(avahi.DBUS_NAME,
300
self.server.EntryGroupNew()),
301
avahi.DBUS_INTERFACE_ENTRY_GROUP)
302
self.entry_group_state_changed_match = (
303
self.group.connect_to_signal(
304
'StateChanged', self.entry_group_state_changed))
305
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
306
self.name, self.type)
307
self.group.AddService(
310
dbus.UInt32(0), # flags
311
self.name, self.type,
312
self.domain, self.host,
313
dbus.UInt16(self.port),
314
avahi.string_array_to_txt_array(self.TXT))
317
def entry_group_state_changed(self, state, error):
318
"""Derived from the Avahi example code"""
319
logger.debug("Avahi entry group state change: %i", state)
321
if state == avahi.ENTRY_GROUP_ESTABLISHED:
322
logger.debug("Zeroconf service established.")
323
elif state == avahi.ENTRY_GROUP_COLLISION:
324
logger.info("Zeroconf service name collision.")
326
elif state == avahi.ENTRY_GROUP_FAILURE:
327
logger.critical("Avahi: Error in group state changed %s",
329
raise AvahiGroupError("State changed: {0!s}"
333
"""Derived from the Avahi example code"""
334
if self.group is not None:
337
except (dbus.exceptions.UnknownMethodException,
338
dbus.exceptions.DBusException):
343
def server_state_changed(self, state, error=None):
344
"""Derived from the Avahi example code"""
345
logger.debug("Avahi server state change: %i", state)
346
bad_states = { avahi.SERVER_INVALID:
347
"Zeroconf server invalid",
348
avahi.SERVER_REGISTERING: None,
349
avahi.SERVER_COLLISION:
350
"Zeroconf server name collision",
351
avahi.SERVER_FAILURE:
352
"Zeroconf server failure" }
353
if state in bad_states:
354
if bad_states[state] is not None:
356
logger.error(bad_states[state])
358
logger.error(bad_states[state] + ": %r", error)
360
elif state == avahi.SERVER_RUNNING:
364
logger.debug("Unknown state: %r", state)
366
logger.debug("Unknown state: %r: %r", state, error)
369
"""Derived from the Avahi example code"""
370
if self.server is None:
371
self.server = dbus.Interface(
372
self.bus.get_object(avahi.DBUS_NAME,
373
avahi.DBUS_PATH_SERVER,
374
follow_name_owner_changes=True),
375
avahi.DBUS_INTERFACE_SERVER)
376
self.server.connect_to_signal("StateChanged",
377
self.server_state_changed)
378
self.server_state_changed(self.server.GetState())
380
class AvahiServiceToSyslog(AvahiService):
382
"""Add the new name to the syslog messages"""
383
ret = AvahiService.rename(self)
384
syslogger.setFormatter(logging.Formatter
385
('Mandos ({0}) [%(process)d]:'
386
' %(levelname)s: %(message)s'
390
def timedelta_to_milliseconds(td):
391
"Convert a datetime.timedelta() to milliseconds"
392
return ((td.days * 24 * 60 * 60 * 1000)
393
+ (td.seconds * 1000)
394
+ (td.microseconds // 1000))
396
class Client(object):
151
group = dbus.Interface(bus.get_object
153
server.EntryGroupNew()),
154
avahi.DBUS_INTERFACE_ENTRY_GROUP)
155
group.connect_to_signal('StateChanged',
156
entry_group_state_changed)
157
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
158
service.name, service.type)
160
self.interface, # interface
161
avahi.PROTO_INET6, # protocol
162
dbus.UInt32(0), # flags
163
self.name, self.type,
164
self.domain, self.host,
165
dbus.UInt16(self.port),
166
avahi.string_array_to_txt_array(self.TXT))
169
# From the Avahi example code:
170
group = None # our entry group
171
# End of Avahi example code
174
def _datetime_to_dbus(dt, variant_level=0):
175
"""Convert a UTC datetime.datetime() to a D-Bus type."""
176
return dbus.String(dt.isoformat(), variant_level=variant_level)
179
class Client(dbus.service.Object):
397
180
"""A representation of a client host served by this server.
400
approved: bool(); 'None' if not yet approved/disapproved
401
approval_delay: datetime.timedelta(); Time to wait for approval
402
approval_duration: datetime.timedelta(); Duration of one approval
182
name: string; from the config file, used in log messages and
184
fingerprint: string (40 or 32 hexadecimal digits); used to
185
uniquely identify the client
186
secret: bytestring; sent verbatim (over TLS) to client
187
host: string; available for use by the checker command
188
created: datetime.datetime(); (UTC) object creation
189
last_enabled: datetime.datetime(); (UTC)
191
last_checked_ok: datetime.datetime(); (UTC) or None
192
timeout: datetime.timedelta(); How long from last_checked_ok
193
until this client is invalid
194
interval: datetime.timedelta(); How often to start a new checker
195
disable_hook: If set, called by disable() as disable_hook(self)
403
196
checker: subprocess.Popen(); a running checker process used
404
197
to see if the client lives.
405
198
'None' if no process is running.
406
checker_callback_tag: a gobject event source tag, or None
407
checker_command: string; External command which is run to check
408
if client lives. %() expansions are done at
199
checker_initiator_tag: a gobject event source tag, or None
200
disable_initiator_tag: - '' -
201
checker_callback_tag: - '' -
202
checker_command: string; External command which is run to check if
203
client lives. %() expansions are done at
409
204
runtime with vars(self) as dict, so that for
410
205
instance %(name)s can be used in the command.
411
checker_initiator_tag: a gobject event source tag, or None
412
created: datetime.datetime(); (UTC) object creation
413
client_structure: Object describing what attributes a client has
414
and is used for storing the client at exit
415
current_checker_command: string; current running checker_command
416
disable_initiator_tag: a gobject event source tag, or None
418
fingerprint: string (40 or 32 hexadecimal digits); used to
419
uniquely identify the client
420
host: string; available for use by the checker command
421
interval: datetime.timedelta(); How often to start a new checker
422
last_approval_request: datetime.datetime(); (UTC) or None
423
last_checked_ok: datetime.datetime(); (UTC) or None
424
last_checker_status: integer between 0 and 255 reflecting exit
425
status of last checker. -1 reflects crashed
426
checker, -2 means no checker completed yet.
427
last_enabled: datetime.datetime(); (UTC) or None
428
name: string; from the config file, used in log messages and
430
secret: bytestring; sent verbatim (over TLS) to client
431
timeout: datetime.timedelta(); How long from last_checked_ok
432
until this client is disabled
433
extended_timeout: extra long timeout when secret has been sent
434
runtime_expansions: Allowed attributes for runtime expansion.
435
expires: datetime.datetime(); time (UTC) when a client will be
206
use_dbus: bool(); Whether to provide D-Bus interface and signals
207
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
439
runtime_expansions = ("approval_delay", "approval_duration",
440
"created", "enabled", "fingerprint",
441
"host", "interval", "last_checked_ok",
442
"last_enabled", "name", "timeout")
443
client_defaults = { "timeout": "5m",
444
"extended_timeout": "15m",
446
"checker": "fping -q -- %%(host)s",
448
"approval_delay": "0s",
449
"approval_duration": "1s",
450
"approved_by_default": "True",
454
209
def timeout_milliseconds(self):
455
210
"Return the 'timeout' attribute in milliseconds"
456
return timedelta_to_milliseconds(self.timeout)
458
def extended_timeout_milliseconds(self):
459
"Return the 'extended_timeout' attribute in milliseconds"
460
return timedelta_to_milliseconds(self.extended_timeout)
211
return ((self.timeout.days * 24 * 60 * 60 * 1000)
212
+ (self.timeout.seconds * 1000)
213
+ (self.timeout.microseconds // 1000))
462
215
def interval_milliseconds(self):
463
216
"Return the 'interval' attribute in milliseconds"
464
return timedelta_to_milliseconds(self.interval)
466
def approval_delay_milliseconds(self):
467
return timedelta_to_milliseconds(self.approval_delay)
470
def config_parser(config):
471
"""Construct a new dict of client settings of this form:
472
{ client_name: {setting_name: value, ...}, ...}
473
with exceptions for any special settings as defined above.
474
NOTE: Must be a pure function. Must return the same result
475
value given the same arguments.
478
for client_name in config.sections():
479
section = dict(config.items(client_name))
480
client = settings[client_name] = {}
482
client["host"] = section["host"]
483
# Reformat values from string types to Python types
484
client["approved_by_default"] = config.getboolean(
485
client_name, "approved_by_default")
486
client["enabled"] = config.getboolean(client_name,
489
client["fingerprint"] = (section["fingerprint"].upper()
491
if "secret" in section:
492
client["secret"] = section["secret"].decode("base64")
493
elif "secfile" in section:
494
with open(os.path.expanduser(os.path.expandvars
495
(section["secfile"])),
497
client["secret"] = secfile.read()
499
raise TypeError("No secret or secfile for section {0}"
501
client["timeout"] = string_to_delta(section["timeout"])
502
client["extended_timeout"] = string_to_delta(
503
section["extended_timeout"])
504
client["interval"] = string_to_delta(section["interval"])
505
client["approval_delay"] = string_to_delta(
506
section["approval_delay"])
507
client["approval_duration"] = string_to_delta(
508
section["approval_duration"])
509
client["checker_command"] = section["checker"]
510
client["last_approval_request"] = None
511
client["last_checked_ok"] = None
512
client["last_checker_status"] = -2
516
def __init__(self, settings, name = None):
217
return ((self.interval.days * 24 * 60 * 60 * 1000)
218
+ (self.interval.seconds * 1000)
219
+ (self.interval.microseconds // 1000))
221
def __init__(self, name = None, disable_hook=None, config=None,
223
"""Note: the 'checker' key in 'config' sets the
224
'checker_command' attribute and *not* the 'checker'
518
# adding all client settings
519
for setting, value in settings.iteritems():
520
setattr(self, setting, value)
523
if not hasattr(self, "last_enabled"):
524
self.last_enabled = datetime.datetime.utcnow()
525
if not hasattr(self, "expires"):
526
self.expires = (datetime.datetime.utcnow()
529
self.last_enabled = None
532
logger.debug("Creating client %r", self.name)
229
logger.debug(u"Creating client %r", self.name)
230
self.use_dbus = False # During __init__
533
231
# Uppercase and remove spaces from fingerprint for later
534
232
# comparison purposes with return value from the fingerprint()
536
logger.debug(" Fingerprint: %s", self.fingerprint)
537
self.created = settings.get("created",
538
datetime.datetime.utcnow())
540
# 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
with closing(open(os.path.expanduser
242
(config["secfile"])))) as secfile:
243
self.secret = secfile.read()
245
raise TypeError(u"No secret or secfile for client %s"
247
self.host = config.get("host", "")
248
self.created = datetime.datetime.utcnow()
250
self.last_enabled = None
251
self.last_checked_ok = None
252
self.timeout = string_to_delta(config["timeout"])
253
self.interval = string_to_delta(config["interval"])
254
self.disable_hook = disable_hook
541
255
self.checker = None
542
256
self.checker_initiator_tag = None
543
257
self.disable_initiator_tag = None
544
258
self.checker_callback_tag = None
545
self.current_checker_command = None
547
self.approvals_pending = 0
548
self.changedstate = (multiprocessing_manager
549
.Condition(multiprocessing_manager
551
self.client_structure = [attr for attr in
552
self.__dict__.iterkeys()
553
if not attr.startswith("_")]
554
self.client_structure.append("client_structure")
556
for name, t in inspect.getmembers(type(self),
560
if not name.startswith("_"):
561
self.client_structure.append(name)
563
# Send notice to process children that client state has changed
564
def send_changedstate(self):
565
with self.changedstate:
566
self.changedstate.notify_all()
259
self.checker_command = config["checker"]
260
self.last_connect = None
261
# Only now, when this client is initialized, can it show up on
263
self.use_dbus = use_dbus
265
self.dbus_object_path = (dbus.ObjectPath
267
+ self.name.replace(".", "_")))
268
dbus.service.Object.__init__(self, bus,
269
self.dbus_object_path)
568
271
def enable(self):
569
272
"""Start this client's checker and timeout hooks"""
570
if getattr(self, "enabled", False):
573
self.send_changedstate()
574
self.expires = datetime.datetime.utcnow() + self.timeout
576
273
self.last_enabled = datetime.datetime.utcnow()
579
def disable(self, quiet=True):
580
"""Disable this client."""
581
if not getattr(self, "enabled", False):
584
self.send_changedstate()
586
logger.info("Disabling client %s", self.name)
587
if getattr(self, "disable_initiator_tag", False):
588
gobject.source_remove(self.disable_initiator_tag)
589
self.disable_initiator_tag = None
591
if getattr(self, "checker_initiator_tag", False):
592
gobject.source_remove(self.checker_initiator_tag)
593
self.checker_initiator_tag = None
596
# Do not run this again if called by a gobject.timeout_add
602
def init_checker(self):
603
274
# Schedule a new checker to be started an 'interval' from now,
604
275
# and every interval from then on.
605
276
self.checker_initiator_tag = (gobject.timeout_add
606
277
(self.interval_milliseconds(),
607
278
self.start_checker))
279
# Also start a new checker *right now*.
608
281
# Schedule a disable() when 'timeout' has passed
609
282
self.disable_initiator_tag = (gobject.timeout_add
610
283
(self.timeout_milliseconds(),
612
# Also start a new checker *right now*.
288
self.PropertyChanged(dbus.String(u"enabled"),
289
dbus.Boolean(True, variant_level=1))
290
self.PropertyChanged(dbus.String(u"last_enabled"),
291
(_datetime_to_dbus(self.last_enabled,
295
"""Disable this client."""
296
if not getattr(self, "enabled", False):
298
logger.info(u"Disabling client %s", self.name)
299
if getattr(self, "disable_initiator_tag", False):
300
gobject.source_remove(self.disable_initiator_tag)
301
self.disable_initiator_tag = None
302
if getattr(self, "checker_initiator_tag", False):
303
gobject.source_remove(self.checker_initiator_tag)
304
self.checker_initiator_tag = None
306
if self.disable_hook:
307
self.disable_hook(self)
311
self.PropertyChanged(dbus.String(u"enabled"),
312
dbus.Boolean(False, variant_level=1))
313
# Do not run this again if called by a gobject.timeout_add
317
self.disable_hook = None
615
320
def checker_callback(self, pid, condition, command):
616
321
"""The checker has completed, so take appropriate actions."""
617
322
self.checker_callback_tag = None
618
323
self.checker = None
326
self.PropertyChanged(dbus.String(u"checker_running"),
327
dbus.Boolean(False, variant_level=1))
619
328
if os.WIFEXITED(condition):
620
self.last_checker_status = os.WEXITSTATUS(condition)
621
if self.last_checker_status == 0:
622
logger.info("Checker for %(name)s succeeded",
329
exitstatus = os.WEXITSTATUS(condition)
331
logger.info(u"Checker for %(name)s succeeded",
624
333
self.checked_ok()
626
logger.info("Checker for %(name)s failed",
335
logger.info(u"Checker for %(name)s failed",
339
self.CheckerCompleted(dbus.Int16(exitstatus),
340
dbus.Int64(condition),
341
dbus.String(command))
629
self.last_checker_status = -1
630
logger.warning("Checker for %(name)s crashed?",
343
logger.warning(u"Checker for %(name)s crashed?",
347
self.CheckerCompleted(dbus.Int16(-1),
348
dbus.Int64(condition),
349
dbus.String(command))
633
351
def checked_ok(self):
634
"""Assert that the client has been seen, alive and well."""
352
"""Bump up the timeout for this client.
353
This should only be called when the client has been seen,
635
356
self.last_checked_ok = datetime.datetime.utcnow()
636
self.last_checker_status = 0
639
def bump_timeout(self, timeout=None):
640
"""Bump up the timeout for this client."""
642
timeout = self.timeout
643
if self.disable_initiator_tag is not None:
644
gobject.source_remove(self.disable_initiator_tag)
645
if getattr(self, "enabled", False):
646
self.disable_initiator_tag = (gobject.timeout_add
647
(timedelta_to_milliseconds
648
(timeout), self.disable))
649
self.expires = datetime.datetime.utcnow() + timeout
651
def need_approval(self):
652
self.last_approval_request = datetime.datetime.utcnow()
357
gobject.source_remove(self.disable_initiator_tag)
358
self.disable_initiator_tag = (gobject.timeout_add
359
(self.timeout_milliseconds(),
363
self.PropertyChanged(
364
dbus.String(u"last_checked_ok"),
365
(_datetime_to_dbus(self.last_checked_ok,
654
368
def start_checker(self):
655
369
"""Start a new checker subprocess if one is not running.
657
370
If a checker already exists, leave it running and do
659
372
# The reason for not killing a running checker is that if we
733
425
self.checker_callback_tag = None
734
426
if getattr(self, "checker", None) is None:
736
logger.debug("Stopping checker for %(name)s", vars(self))
428
logger.debug(u"Stopping checker for %(name)s", vars(self))
738
self.checker.terminate()
430
os.kill(self.checker.pid, signal.SIGTERM)
740
432
#if self.checker.poll() is None:
741
# self.checker.kill()
742
except OSError as error:
433
# os.kill(self.checker.pid, signal.SIGKILL)
434
except OSError, error:
743
435
if error.errno != errno.ESRCH: # No such process
745
437
self.checker = None
748
def dbus_service_property(dbus_interface, signature="v",
749
access="readwrite", byte_arrays=False):
750
"""Decorators for marking methods of a DBusObjectWithProperties to
751
become properties on the D-Bus.
753
The decorated method will be called with no arguments by "Get"
754
and with one argument by "Set".
756
The parameters, where they are supported, are the same as
757
dbus.service.method, except there is only "signature", since the
758
type from Get() and the type sent to Set() is the same.
760
# Encoding deeply encoded byte arrays is not supported yet by the
761
# "Set" method, so we fail early here:
762
if byte_arrays and signature != "ay":
763
raise ValueError("Byte arrays not supported for non-'ay'"
764
" signature {0!r}".format(signature))
766
func._dbus_is_property = True
767
func._dbus_interface = dbus_interface
768
func._dbus_signature = signature
769
func._dbus_access = access
770
func._dbus_name = func.__name__
771
if func._dbus_name.endswith("_dbus_property"):
772
func._dbus_name = func._dbus_name[:-14]
773
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
778
def dbus_interface_annotations(dbus_interface):
779
"""Decorator for marking functions returning interface annotations
783
@dbus_interface_annotations("org.example.Interface")
784
def _foo(self): # Function name does not matter
785
return {"org.freedesktop.DBus.Deprecated": "true",
786
"org.freedesktop.DBus.Property.EmitsChangedSignal":
790
func._dbus_is_interface = True
791
func._dbus_interface = dbus_interface
792
func._dbus_name = dbus_interface
797
def dbus_annotations(annotations):
798
"""Decorator to annotate D-Bus methods, signals or properties
801
@dbus_service_property("org.example.Interface", signature="b",
803
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
804
"org.freedesktop.DBus.Property."
805
"EmitsChangedSignal": "false"})
806
def Property_dbus_property(self):
807
return dbus.Boolean(False)
810
func._dbus_annotations = annotations
815
class DBusPropertyException(dbus.exceptions.DBusException):
816
"""A base class for D-Bus property-related exceptions
818
def __unicode__(self):
819
return unicode(str(self))
822
class DBusPropertyAccessException(DBusPropertyException):
823
"""A property's access permissions disallows an operation.
828
class DBusPropertyNotFound(DBusPropertyException):
829
"""An attempt was made to access a non-existing property.
834
class DBusObjectWithProperties(dbus.service.Object):
835
"""A D-Bus object with properties.
837
Classes inheriting from this can use the dbus_service_property
838
decorator to expose methods as D-Bus properties. It exposes the
839
standard Get(), Set(), and GetAll() methods on the D-Bus.
843
def _is_dbus_thing(thing):
844
"""Returns a function testing if an attribute is a D-Bus thing
846
If called like _is_dbus_thing("method") it returns a function
847
suitable for use as predicate to inspect.getmembers().
849
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
852
def _get_all_dbus_things(self, thing):
853
"""Returns a generator of (name, attribute) pairs
855
return ((getattr(athing.__get__(self), "_dbus_name",
857
athing.__get__(self))
858
for cls in self.__class__.__mro__
860
inspect.getmembers(cls,
861
self._is_dbus_thing(thing)))
863
def _get_dbus_property(self, interface_name, property_name):
864
"""Returns a bound method if one exists which is a D-Bus
865
property with the specified name and interface.
867
for cls in self.__class__.__mro__:
868
for name, value in (inspect.getmembers
870
self._is_dbus_thing("property"))):
871
if (value._dbus_name == property_name
872
and value._dbus_interface == interface_name):
873
return value.__get__(self)
876
raise DBusPropertyNotFound(self.dbus_object_path + ":"
877
+ interface_name + "."
880
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
882
def Get(self, interface_name, property_name):
883
"""Standard D-Bus property Get() method, see D-Bus standard.
885
prop = self._get_dbus_property(interface_name, property_name)
886
if prop._dbus_access == "write":
887
raise DBusPropertyAccessException(property_name)
889
if not hasattr(value, "variant_level"):
891
return type(value)(value, variant_level=value.variant_level+1)
893
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
894
def Set(self, interface_name, property_name, value):
895
"""Standard D-Bus property Set() method, see D-Bus standard.
897
prop = self._get_dbus_property(interface_name, property_name)
898
if prop._dbus_access == "read":
899
raise DBusPropertyAccessException(property_name)
900
if prop._dbus_get_args_options["byte_arrays"]:
901
# The byte_arrays option is not supported yet on
902
# signatures other than "ay".
903
if prop._dbus_signature != "ay":
905
value = dbus.ByteArray(b''.join(chr(byte)
909
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
910
out_signature="a{sv}")
911
def GetAll(self, interface_name):
912
"""Standard D-Bus property GetAll() method, see D-Bus
915
Note: Will not include properties with access="write".
918
for name, prop in self._get_all_dbus_things("property"):
920
and interface_name != prop._dbus_interface):
921
# Interface non-empty but did not match
923
# Ignore write-only properties
924
if prop._dbus_access == "write":
927
if not hasattr(value, "variant_level"):
928
properties[name] = value
930
properties[name] = type(value)(value, variant_level=
931
value.variant_level+1)
932
return dbus.Dictionary(properties, signature="sv")
934
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
936
path_keyword='object_path',
937
connection_keyword='connection')
938
def Introspect(self, object_path, connection):
939
"""Overloading of standard D-Bus method.
941
Inserts property tags and interface annotation tags.
943
xmlstring = dbus.service.Object.Introspect(self, object_path,
946
document = xml.dom.minidom.parseString(xmlstring)
947
def make_tag(document, name, prop):
948
e = document.createElement("property")
949
e.setAttribute("name", name)
950
e.setAttribute("type", prop._dbus_signature)
951
e.setAttribute("access", prop._dbus_access)
953
for if_tag in document.getElementsByTagName("interface"):
955
for tag in (make_tag(document, name, prop)
957
in self._get_all_dbus_things("property")
958
if prop._dbus_interface
959
== if_tag.getAttribute("name")):
960
if_tag.appendChild(tag)
961
# Add annotation tags
962
for typ in ("method", "signal", "property"):
963
for tag in if_tag.getElementsByTagName(typ):
965
for name, prop in (self.
966
_get_all_dbus_things(typ)):
967
if (name == tag.getAttribute("name")
968
and prop._dbus_interface
969
== if_tag.getAttribute("name")):
970
annots.update(getattr
974
for name, value in annots.iteritems():
975
ann_tag = document.createElement(
977
ann_tag.setAttribute("name", name)
978
ann_tag.setAttribute("value", value)
979
tag.appendChild(ann_tag)
980
# Add interface annotation tags
981
for annotation, value in dict(
983
*(annotations().iteritems()
984
for name, annotations in
985
self._get_all_dbus_things("interface")
986
if name == if_tag.getAttribute("name")
988
ann_tag = document.createElement("annotation")
989
ann_tag.setAttribute("name", annotation)
990
ann_tag.setAttribute("value", value)
991
if_tag.appendChild(ann_tag)
992
# Add the names to the return values for the
993
# "org.freedesktop.DBus.Properties" methods
994
if (if_tag.getAttribute("name")
995
== "org.freedesktop.DBus.Properties"):
996
for cn in if_tag.getElementsByTagName("method"):
997
if cn.getAttribute("name") == "Get":
998
for arg in cn.getElementsByTagName("arg"):
999
if (arg.getAttribute("direction")
1001
arg.setAttribute("name", "value")
1002
elif cn.getAttribute("name") == "GetAll":
1003
for arg in cn.getElementsByTagName("arg"):
1004
if (arg.getAttribute("direction")
1006
arg.setAttribute("name", "props")
1007
xmlstring = document.toxml("utf-8")
1009
except (AttributeError, xml.dom.DOMException,
1010
xml.parsers.expat.ExpatError) as error:
1011
logger.error("Failed to override Introspection method",
1016
def datetime_to_dbus (dt, variant_level=0):
1017
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1019
return dbus.String("", variant_level = variant_level)
1020
return dbus.String(dt.isoformat(),
1021
variant_level=variant_level)
1024
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
1026
"""Applied to an empty subclass of a D-Bus object, this metaclass
1027
will add additional D-Bus attributes matching a certain pattern.
1029
def __new__(mcs, name, bases, attr):
1030
# Go through all the base classes which could have D-Bus
1031
# methods, signals, or properties in them
1032
old_interface_names = []
1033
for base in (b for b in bases
1034
if issubclass(b, dbus.service.Object)):
1035
# Go though all attributes of the base class
1036
for attrname, attribute in inspect.getmembers(base):
1037
# Ignore non-D-Bus attributes, and D-Bus attributes
1038
# with the wrong interface name
1039
if (not hasattr(attribute, "_dbus_interface")
1040
or not attribute._dbus_interface
1041
.startswith("se.recompile.Mandos")):
1043
# Create an alternate D-Bus interface name based on
1045
alt_interface = (attribute._dbus_interface
1046
.replace("se.recompile.Mandos",
1047
"se.bsnet.fukt.Mandos"))
1048
if alt_interface != attribute._dbus_interface:
1049
old_interface_names.append(alt_interface)
1050
# Is this a D-Bus signal?
1051
if getattr(attribute, "_dbus_is_signal", False):
1052
# Extract the original non-method function by
1054
nonmethod_func = (dict(
1055
zip(attribute.func_code.co_freevars,
1056
attribute.__closure__))["func"]
1058
# Create a new, but exactly alike, function
1059
# object, and decorate it to be a new D-Bus signal
1060
# with the alternate D-Bus interface name
1061
new_function = (dbus.service.signal
1063
attribute._dbus_signature)
1064
(types.FunctionType(
1065
nonmethod_func.func_code,
1066
nonmethod_func.func_globals,
1067
nonmethod_func.func_name,
1068
nonmethod_func.func_defaults,
1069
nonmethod_func.func_closure)))
1070
# Copy annotations, if any
1072
new_function._dbus_annotations = (
1073
dict(attribute._dbus_annotations))
1074
except AttributeError:
1076
# Define a creator of a function to call both the
1077
# old and new functions, so both the old and new
1078
# signals gets sent when the function is called
1079
def fixscope(func1, func2):
1080
"""This function is a scope container to pass
1081
func1 and func2 to the "call_both" function
1082
outside of its arguments"""
1083
def call_both(*args, **kwargs):
1084
"""This function will emit two D-Bus
1085
signals by calling func1 and func2"""
1086
func1(*args, **kwargs)
1087
func2(*args, **kwargs)
1089
# Create the "call_both" function and add it to
1091
attr[attrname] = fixscope(attribute,
1093
# Is this a D-Bus method?
1094
elif getattr(attribute, "_dbus_is_method", False):
1095
# Create a new, but exactly alike, function
1096
# object. Decorate it to be a new D-Bus method
1097
# with the alternate D-Bus interface name. Add it
1099
attr[attrname] = (dbus.service.method
1101
attribute._dbus_in_signature,
1102
attribute._dbus_out_signature)
1104
(attribute.func_code,
1105
attribute.func_globals,
1106
attribute.func_name,
1107
attribute.func_defaults,
1108
attribute.func_closure)))
1109
# Copy annotations, if any
1111
attr[attrname]._dbus_annotations = (
1112
dict(attribute._dbus_annotations))
1113
except AttributeError:
1115
# Is this a D-Bus property?
1116
elif getattr(attribute, "_dbus_is_property", False):
1117
# Create a new, but exactly alike, function
1118
# object, and decorate it to be a new D-Bus
1119
# property with the alternate D-Bus interface
1120
# name. Add it to the class.
1121
attr[attrname] = (dbus_service_property
1123
attribute._dbus_signature,
1124
attribute._dbus_access,
1126
._dbus_get_args_options
1129
(attribute.func_code,
1130
attribute.func_globals,
1131
attribute.func_name,
1132
attribute.func_defaults,
1133
attribute.func_closure)))
1134
# Copy annotations, if any
1136
attr[attrname]._dbus_annotations = (
1137
dict(attribute._dbus_annotations))
1138
except AttributeError:
1140
# Is this a D-Bus interface?
1141
elif getattr(attribute, "_dbus_is_interface", False):
1142
# Create a new, but exactly alike, function
1143
# object. Decorate it to be a new D-Bus interface
1144
# with the alternate D-Bus interface name. Add it
1146
attr[attrname] = (dbus_interface_annotations
1149
(attribute.func_code,
1150
attribute.func_globals,
1151
attribute.func_name,
1152
attribute.func_defaults,
1153
attribute.func_closure)))
1154
# Deprecate all old interfaces
1155
iname="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1156
for old_interface_name in old_interface_names:
1157
@dbus_interface_annotations(old_interface_name)
1159
return { "org.freedesktop.DBus.Deprecated": "true" }
1160
# Find an unused name
1161
for aname in (iname.format(i) for i in itertools.count()):
1162
if aname not in attr:
1165
return type.__new__(mcs, name, bases, attr)
1168
class ClientDBus(Client, DBusObjectWithProperties):
1169
"""A Client class using D-Bus
1172
dbus_object_path: dbus.ObjectPath
1173
bus: dbus.SystemBus()
1176
runtime_expansions = (Client.runtime_expansions
1177
+ ("dbus_object_path",))
1179
# dbus.service.Object doesn't use super(), so we can't either.
1181
def __init__(self, bus = None, *args, **kwargs):
1183
Client.__init__(self, *args, **kwargs)
1184
# Only now, when this client is initialized, can it show up on
1186
client_object_name = unicode(self.name).translate(
1187
{ord("."): ord("_"),
1188
ord("-"): ord("_")})
1189
self.dbus_object_path = (dbus.ObjectPath
1190
("/clients/" + client_object_name))
1191
DBusObjectWithProperties.__init__(self, self.bus,
1192
self.dbus_object_path)
1194
def notifychangeproperty(transform_func,
1195
dbus_name, type_func=lambda x: x,
1197
""" Modify a variable so that it's a property which announces
1198
its changes to DBus.
1200
transform_fun: Function that takes a value and a variant_level
1201
and transforms it to a D-Bus type.
1202
dbus_name: D-Bus name of the variable
1203
type_func: Function that transform the value before sending it
1204
to the D-Bus. Default: no transform
1205
variant_level: D-Bus variant level. Default: 1
1207
attrname = "_{0}".format(dbus_name)
1208
def setter(self, value):
1209
if hasattr(self, "dbus_object_path"):
1210
if (not hasattr(self, attrname) or
1211
type_func(getattr(self, attrname, None))
1212
!= type_func(value)):
1213
dbus_value = transform_func(type_func(value),
1216
self.PropertyChanged(dbus.String(dbus_name),
1218
setattr(self, attrname, value)
1220
return property(lambda self: getattr(self, attrname), setter)
1222
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1223
approvals_pending = notifychangeproperty(dbus.Boolean,
1226
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1227
last_enabled = notifychangeproperty(datetime_to_dbus,
1229
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1230
type_func = lambda checker:
1231
checker is not None)
1232
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1234
last_checker_status = notifychangeproperty(dbus.Int16,
1235
"LastCheckerStatus")
1236
last_approval_request = notifychangeproperty(
1237
datetime_to_dbus, "LastApprovalRequest")
1238
approved_by_default = notifychangeproperty(dbus.Boolean,
1239
"ApprovedByDefault")
1240
approval_delay = notifychangeproperty(dbus.UInt64,
1243
timedelta_to_milliseconds)
1244
approval_duration = notifychangeproperty(
1245
dbus.UInt64, "ApprovalDuration",
1246
type_func = timedelta_to_milliseconds)
1247
host = notifychangeproperty(dbus.String, "Host")
1248
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1250
timedelta_to_milliseconds)
1251
extended_timeout = notifychangeproperty(
1252
dbus.UInt64, "ExtendedTimeout",
1253
type_func = timedelta_to_milliseconds)
1254
interval = notifychangeproperty(dbus.UInt64,
1257
timedelta_to_milliseconds)
1258
checker_command = notifychangeproperty(dbus.String, "Checker")
1260
del notifychangeproperty
1262
def __del__(self, *args, **kwargs):
1264
self.remove_from_connection()
1267
if hasattr(DBusObjectWithProperties, "__del__"):
1268
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1269
Client.__del__(self, *args, **kwargs)
1271
def checker_callback(self, pid, condition, command,
1273
self.checker_callback_tag = None
1275
if os.WIFEXITED(condition):
1276
exitstatus = os.WEXITSTATUS(condition)
1278
self.CheckerCompleted(dbus.Int16(exitstatus),
1279
dbus.Int64(condition),
1280
dbus.String(command))
1283
self.CheckerCompleted(dbus.Int16(-1),
1284
dbus.Int64(condition),
1285
dbus.String(command))
1287
return Client.checker_callback(self, pid, condition, command,
1290
def start_checker(self, *args, **kwargs):
1291
old_checker = self.checker
1292
if self.checker is not None:
1293
old_checker_pid = self.checker.pid
1295
old_checker_pid = None
1296
r = Client.start_checker(self, *args, **kwargs)
1297
# Only if new checker process was started
1298
if (self.checker is not None
1299
and old_checker_pid != self.checker.pid):
1301
self.CheckerStarted(self.current_checker_command)
1304
def _reset_approved(self):
1305
self.approved = None
1308
def approve(self, value=True):
1309
self.send_changedstate()
1310
self.approved = value
1311
gobject.timeout_add(timedelta_to_milliseconds
1312
(self.approval_duration),
1313
self._reset_approved)
1315
## D-Bus methods, signals & properties
1316
_interface = "se.recompile.Mandos.Client"
1320
@dbus_interface_annotations(_interface)
1322
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
439
self.PropertyChanged(dbus.String(u"checker_running"),
440
dbus.Boolean(False, variant_level=1))
442
def still_valid(self):
443
"""Has the timeout not yet passed for this client?"""
444
if not getattr(self, "enabled", False):
446
now = datetime.datetime.utcnow()
447
if self.last_checked_ok is None:
448
return now < (self.created + self.timeout)
450
return now < (self.last_checked_ok + self.timeout)
452
## D-Bus methods & signals
453
_interface = u"se.bsnet.fukt.Mandos.Client"
456
CheckedOK = dbus.service.method(_interface)(checked_ok)
457
CheckedOK.__name__ = "CheckedOK"
1327
459
# CheckerCompleted - signal
1328
460
@dbus.service.signal(_interface, signature="nxs")
1396
584
# StopChecker - method
1397
@dbus.service.method(_interface)
1398
def StopChecker(self):
1403
# ApprovalPending - property
1404
@dbus_service_property(_interface, signature="b", access="read")
1405
def ApprovalPending_dbus_property(self):
1406
return dbus.Boolean(bool(self.approvals_pending))
1408
# ApprovedByDefault - property
1409
@dbus_service_property(_interface, signature="b",
1411
def ApprovedByDefault_dbus_property(self, value=None):
1412
if value is None: # get
1413
return dbus.Boolean(self.approved_by_default)
1414
self.approved_by_default = bool(value)
1416
# ApprovalDelay - property
1417
@dbus_service_property(_interface, signature="t",
1419
def ApprovalDelay_dbus_property(self, value=None):
1420
if value is None: # get
1421
return dbus.UInt64(self.approval_delay_milliseconds())
1422
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1424
# ApprovalDuration - property
1425
@dbus_service_property(_interface, signature="t",
1427
def ApprovalDuration_dbus_property(self, value=None):
1428
if value is None: # get
1429
return dbus.UInt64(timedelta_to_milliseconds(
1430
self.approval_duration))
1431
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1434
@dbus_service_property(_interface, signature="s", access="read")
1435
def Name_dbus_property(self):
1436
return dbus.String(self.name)
1438
# Fingerprint - property
1439
@dbus_service_property(_interface, signature="s", access="read")
1440
def Fingerprint_dbus_property(self):
1441
return dbus.String(self.fingerprint)
1444
@dbus_service_property(_interface, signature="s",
1446
def Host_dbus_property(self, value=None):
1447
if value is None: # get
1448
return dbus.String(self.host)
1449
self.host = unicode(value)
1451
# Created - property
1452
@dbus_service_property(_interface, signature="s", access="read")
1453
def Created_dbus_property(self):
1454
return datetime_to_dbus(self.created)
1456
# LastEnabled - property
1457
@dbus_service_property(_interface, signature="s", access="read")
1458
def LastEnabled_dbus_property(self):
1459
return datetime_to_dbus(self.last_enabled)
1461
# Enabled - property
1462
@dbus_service_property(_interface, signature="b",
1464
def Enabled_dbus_property(self, value=None):
1465
if value is None: # get
1466
return dbus.Boolean(self.enabled)
1472
# LastCheckedOK - property
1473
@dbus_service_property(_interface, signature="s",
1475
def LastCheckedOK_dbus_property(self, value=None):
1476
if value is not None:
1479
return datetime_to_dbus(self.last_checked_ok)
1481
# LastCheckerStatus - property
1482
@dbus_service_property(_interface, signature="n",
1484
def LastCheckerStatus_dbus_property(self):
1485
return dbus.Int16(self.last_checker_status)
1487
# Expires - property
1488
@dbus_service_property(_interface, signature="s", access="read")
1489
def Expires_dbus_property(self):
1490
return datetime_to_dbus(self.expires)
1492
# LastApprovalRequest - property
1493
@dbus_service_property(_interface, signature="s", access="read")
1494
def LastApprovalRequest_dbus_property(self):
1495
return datetime_to_dbus(self.last_approval_request)
1497
# Timeout - property
1498
@dbus_service_property(_interface, signature="t",
1500
def Timeout_dbus_property(self, value=None):
1501
if value is None: # get
1502
return dbus.UInt64(self.timeout_milliseconds())
1503
self.timeout = datetime.timedelta(0, 0, 0, value)
1504
# Reschedule timeout
1506
now = datetime.datetime.utcnow()
1507
time_to_die = timedelta_to_milliseconds(
1508
(self.last_checked_ok + self.timeout) - now)
1509
if time_to_die <= 0:
1510
# The timeout has passed
1513
self.expires = (now +
1514
datetime.timedelta(milliseconds =
1516
if (getattr(self, "disable_initiator_tag", None)
1519
gobject.source_remove(self.disable_initiator_tag)
1520
self.disable_initiator_tag = (gobject.timeout_add
1524
# ExtendedTimeout - property
1525
@dbus_service_property(_interface, signature="t",
1527
def ExtendedTimeout_dbus_property(self, value=None):
1528
if value is None: # get
1529
return dbus.UInt64(self.extended_timeout_milliseconds())
1530
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1532
# Interval - property
1533
@dbus_service_property(_interface, signature="t",
1535
def Interval_dbus_property(self, value=None):
1536
if value is None: # get
1537
return dbus.UInt64(self.interval_milliseconds())
1538
self.interval = datetime.timedelta(0, 0, 0, value)
1539
if getattr(self, "checker_initiator_tag", None) is None:
1542
# Reschedule checker run
1543
gobject.source_remove(self.checker_initiator_tag)
1544
self.checker_initiator_tag = (gobject.timeout_add
1545
(value, self.start_checker))
1546
self.start_checker() # Start one now, too
1548
# Checker - property
1549
@dbus_service_property(_interface, signature="s",
1551
def Checker_dbus_property(self, value=None):
1552
if value is None: # get
1553
return dbus.String(self.checker_command)
1554
self.checker_command = unicode(value)
1556
# CheckerRunning - property
1557
@dbus_service_property(_interface, signature="b",
1559
def CheckerRunning_dbus_property(self, value=None):
1560
if value is None: # get
1561
return dbus.Boolean(self.checker is not None)
1563
self.start_checker()
1567
# ObjectPath - property
1568
@dbus_service_property(_interface, signature="o", access="read")
1569
def ObjectPath_dbus_property(self):
1570
return self.dbus_object_path # is already a dbus.ObjectPath
1573
@dbus_service_property(_interface, signature="ay",
1574
access="write", byte_arrays=True)
1575
def Secret_dbus_property(self, value):
1576
self.secret = str(value)
585
StopChecker = dbus.service.method(_interface)(stop_checker)
586
StopChecker.__name__ = "StopChecker"
1581
class ProxyClient(object):
1582
def __init__(self, child_pipe, fpr, address):
1583
self._pipe = child_pipe
1584
self._pipe.send(('init', fpr, address))
1585
if not self._pipe.recv():
1588
def __getattribute__(self, name):
1590
return super(ProxyClient, self).__getattribute__(name)
1591
self._pipe.send(('getattr', name))
1592
data = self._pipe.recv()
1593
if data[0] == 'data':
1595
if data[0] == 'function':
1596
def func(*args, **kwargs):
1597
self._pipe.send(('funcall', name, args, kwargs))
1598
return self._pipe.recv()[1]
1601
def __setattr__(self, name, value):
1603
return super(ProxyClient, self).__setattr__(name, value)
1604
self._pipe.send(('setattr', name, value))
1607
class ClientDBusTransitional(ClientDBus):
1608
__metaclass__ = AlternateDBusNamesMetaclass
1611
class ClientHandler(socketserver.BaseRequestHandler, object):
1612
"""A class to handle client connections.
1614
Instantiated once for each connection to handle it.
591
def peer_certificate(session):
592
"Return the peer's OpenPGP certificate as a bytestring"
593
# If not an OpenPGP certificate...
594
if (gnutls.library.functions
595
.gnutls_certificate_type_get(session._c_object)
596
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
597
# ...do the normal thing
598
return session.peer_certificate
599
list_size = ctypes.c_uint(1)
600
cert_list = (gnutls.library.functions
601
.gnutls_certificate_get_peers
602
(session._c_object, ctypes.byref(list_size)))
603
if not bool(cert_list) and list_size.value != 0:
604
raise gnutls.errors.GNUTLSError("error getting peer"
606
if list_size.value == 0:
609
return ctypes.string_at(cert.data, cert.size)
612
def fingerprint(openpgp):
613
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
614
# New GnuTLS "datum" with the OpenPGP public key
615
datum = (gnutls.library.types
616
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
619
ctypes.c_uint(len(openpgp))))
620
# New empty GnuTLS certificate
621
crt = gnutls.library.types.gnutls_openpgp_crt_t()
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
624
# Import the OpenPGP public key into the certificate
625
(gnutls.library.functions
626
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
627
gnutls.library.constants
628
.GNUTLS_OPENPGP_FMT_RAW))
629
# Verify the self signature in the key
630
crtverify = ctypes.c_uint()
631
(gnutls.library.functions
632
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
633
if crtverify.value != 0:
634
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
635
raise gnutls.errors.CertificateSecurityError("Verify failed")
636
# New buffer for the fingerprint
637
buf = ctypes.create_string_buffer(20)
638
buf_len = ctypes.c_size_t()
639
# Get the fingerprint from the certificate into the buffer
640
(gnutls.library.functions
641
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
642
ctypes.byref(buf_len)))
643
# Deinit the certificate
644
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
645
# Convert the buffer to a Python bytestring
646
fpr = ctypes.string_at(buf, buf_len.value)
647
# Convert the bytestring to hexadecimal notation
648
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
652
class TCP_handler(SocketServer.BaseRequestHandler, object):
653
"""A TCP request handler class.
654
Instantiated by IPv6_TCPServer for each request to handle it.
1615
655
Note: This will run in its own forked process."""
1617
657
def handle(self):
1618
with contextlib.closing(self.server.child_pipe) as child_pipe:
1619
logger.info("TCP connection from: %s",
1620
unicode(self.client_address))
1621
logger.debug("Pipe FD: %d",
1622
self.server.child_pipe.fileno())
1624
session = (gnutls.connection
1625
.ClientSession(self.request,
1627
.X509Credentials()))
1629
# Note: gnutls.connection.X509Credentials is really a
1630
# generic GnuTLS certificate credentials object so long as
1631
# no X.509 keys are added to it. Therefore, we can use it
1632
# here despite using OpenPGP certificates.
1634
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1635
# "+AES-256-CBC", "+SHA1",
1636
# "+COMP-NULL", "+CTYPE-OPENPGP",
1638
# Use a fallback default, since this MUST be set.
1639
priority = self.server.gnutls_priority
1640
if priority is None:
1642
(gnutls.library.functions
1643
.gnutls_priority_set_direct(session._c_object,
1646
# Start communication using the Mandos protocol
1647
# Get protocol number
1648
line = self.request.makefile().readline()
1649
logger.debug("Protocol version: %r", line)
1651
if int(line.strip().split()[0]) > 1:
1653
except (ValueError, IndexError, RuntimeError) as error:
1654
logger.error("Unknown protocol version: %s", error)
1657
# Start GnuTLS connection
1660
except gnutls.errors.GNUTLSError as error:
1661
logger.warning("Handshake failed: %s", error)
1662
# Do not run session.bye() here: the session is not
1663
# established. Just abandon the request.
1665
logger.debug("Handshake succeeded")
1667
approval_required = False
1670
fpr = self.fingerprint(self.peer_certificate
1673
gnutls.errors.GNUTLSError) as error:
1674
logger.warning("Bad certificate: %s", error)
1676
logger.debug("Fingerprint: %s", fpr)
1679
client = ProxyClient(child_pipe, fpr,
1680
self.client_address)
1684
if client.approval_delay:
1685
delay = client.approval_delay
1686
client.approvals_pending += 1
1687
approval_required = True
1690
if not client.enabled:
1691
logger.info("Client %s is disabled",
1693
if self.server.use_dbus:
1695
client.Rejected("Disabled")
1698
if client.approved or not client.approval_delay:
1699
#We are approved or approval is disabled
1701
elif client.approved is None:
1702
logger.info("Client %s needs approval",
1704
if self.server.use_dbus:
1706
client.NeedApproval(
1707
client.approval_delay_milliseconds(),
1708
client.approved_by_default)
1710
logger.warning("Client %s was not approved",
1712
if self.server.use_dbus:
1714
client.Rejected("Denied")
1717
#wait until timeout or approved
1718
time = datetime.datetime.now()
1719
client.changedstate.acquire()
1720
(client.changedstate.wait
1721
(float(client.timedelta_to_milliseconds(delay)
1723
client.changedstate.release()
1724
time2 = datetime.datetime.now()
1725
if (time2 - time) >= delay:
1726
if not client.approved_by_default:
1727
logger.warning("Client %s timed out while"
1728
" waiting for approval",
1730
if self.server.use_dbus:
1732
client.Rejected("Approval timed out")
1737
delay -= time2 - time
1740
while sent_size < len(client.secret):
1742
sent = session.send(client.secret[sent_size:])
1743
except gnutls.errors.GNUTLSError as error:
1744
logger.warning("gnutls send failed")
1746
logger.debug("Sent: %d, remaining: %d",
1747
sent, len(client.secret)
1748
- (sent_size + sent))
1751
logger.info("Sending secret to %s", client.name)
1752
# bump the timeout using extended_timeout
1753
client.bump_timeout(client.extended_timeout)
1754
if self.server.use_dbus:
1759
if approval_required:
1760
client.approvals_pending -= 1
1763
except gnutls.errors.GNUTLSError as error:
1764
logger.warning("GnuTLS bye failed")
1767
def peer_certificate(session):
1768
"Return the peer's OpenPGP certificate as a bytestring"
1769
# If not an OpenPGP certificate...
1770
if (gnutls.library.functions
1771
.gnutls_certificate_type_get(session._c_object)
1772
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1773
# ...do the normal thing
1774
return session.peer_certificate
1775
list_size = ctypes.c_uint(1)
1776
cert_list = (gnutls.library.functions
1777
.gnutls_certificate_get_peers
1778
(session._c_object, ctypes.byref(list_size)))
1779
if not bool(cert_list) and list_size.value != 0:
1780
raise gnutls.errors.GNUTLSError("error getting peer"
1782
if list_size.value == 0:
1785
return ctypes.string_at(cert.data, cert.size)
1788
def fingerprint(openpgp):
1789
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1790
# New GnuTLS "datum" with the OpenPGP public key
1791
datum = (gnutls.library.types
1792
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1795
ctypes.c_uint(len(openpgp))))
1796
# New empty GnuTLS certificate
1797
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1798
(gnutls.library.functions
1799
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1800
# Import the OpenPGP public key into the certificate
1801
(gnutls.library.functions
1802
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1803
gnutls.library.constants
1804
.GNUTLS_OPENPGP_FMT_RAW))
1805
# Verify the self signature in the key
1806
crtverify = ctypes.c_uint()
1807
(gnutls.library.functions
1808
.gnutls_openpgp_crt_verify_self(crt, 0,
1809
ctypes.byref(crtverify)))
1810
if crtverify.value != 0:
1811
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1812
raise (gnutls.errors.CertificateSecurityError
1814
# New buffer for the fingerprint
1815
buf = ctypes.create_string_buffer(20)
1816
buf_len = ctypes.c_size_t()
1817
# Get the fingerprint from the certificate into the buffer
1818
(gnutls.library.functions
1819
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1820
ctypes.byref(buf_len)))
1821
# Deinit the certificate
1822
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1823
# Convert the buffer to a Python bytestring
1824
fpr = ctypes.string_at(buf, buf_len.value)
1825
# Convert the bytestring to hexadecimal notation
1826
hex_fpr = binascii.hexlify(fpr).upper()
1830
class MultiprocessingMixIn(object):
1831
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1832
def sub_process_main(self, request, address):
1834
self.finish_request(request, address)
1836
self.handle_error(request, address)
1837
self.close_request(request)
1839
def process_request(self, request, address):
1840
"""Start a new process to process the request."""
1841
proc = multiprocessing.Process(target = self.sub_process_main,
1848
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1849
""" adds a pipe to the MixIn """
1850
def process_request(self, request, client_address):
1851
"""Overrides and wraps the original process_request().
1853
This function creates a new pipe in self.pipe
1855
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1857
proc = MultiprocessingMixIn.process_request(self, request,
1859
self.child_pipe.close()
1860
self.add_pipe(parent_pipe, proc)
1862
def add_pipe(self, parent_pipe, proc):
1863
"""Dummy function; override as necessary"""
1864
raise NotImplementedError
1867
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1868
socketserver.TCPServer, object):
1869
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
658
logger.info(u"TCP connection from: %s",
659
unicode(self.client_address))
660
session = (gnutls.connection
661
.ClientSession(self.request,
665
line = self.request.makefile().readline()
666
logger.debug(u"Protocol version: %r", line)
668
if int(line.strip().split()[0]) > 1:
670
except (ValueError, IndexError, RuntimeError), error:
671
logger.error(u"Unknown protocol version: %s", error)
674
# Note: gnutls.connection.X509Credentials is really a generic
675
# GnuTLS certificate credentials object so long as no X.509
676
# keys are added to it. Therefore, we can use it here despite
677
# using OpenPGP certificates.
679
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
680
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
682
# Use a fallback default, since this MUST be set.
683
priority = self.server.settings.get("priority", "NORMAL")
684
(gnutls.library.functions
685
.gnutls_priority_set_direct(session._c_object,
690
except gnutls.errors.GNUTLSError, error:
691
logger.warning(u"Handshake failed: %s", error)
692
# Do not run session.bye() here: the session is not
693
# established. Just abandon the request.
695
logger.debug(u"Handshake succeeded")
697
fpr = fingerprint(peer_certificate(session))
698
except (TypeError, gnutls.errors.GNUTLSError), error:
699
logger.warning(u"Bad certificate: %s", error)
702
logger.debug(u"Fingerprint: %s", fpr)
704
for c in self.server.clients:
705
if c.fingerprint == fpr:
709
logger.warning(u"Client not found for fingerprint: %s",
713
# Have to check if client.still_valid(), since it is possible
714
# that the client timed out while establishing the GnuTLS
716
if not client.still_valid():
717
logger.warning(u"Client %(name)s is invalid",
721
## This won't work here, since we're in a fork.
722
# client.checked_ok()
724
while sent_size < len(client.secret):
725
sent = session.send(client.secret[sent_size:])
726
logger.debug(u"Sent: %d, remaining: %d",
727
sent, len(client.secret)
728
- (sent_size + sent))
733
class IPv6_TCPServer(SocketServer.ForkingMixIn,
734
SocketServer.TCPServer, object):
735
"""IPv6 TCP server. Accepts 'None' as address and/or port.
737
settings: Server settings
738
clients: Set() of Client objects
1872
739
enabled: Boolean; whether this server is activated yet
1873
interface: None or a network interface name (string)
1874
use_ipv6: Boolean; to use IPv6 or not
1876
def __init__(self, server_address, RequestHandlerClass,
1877
interface=None, use_ipv6=True):
1878
self.interface = interface
1880
self.address_family = socket.AF_INET6
1881
socketserver.TCPServer.__init__(self, server_address,
1882
RequestHandlerClass)
741
address_family = socket.AF_INET6
742
def __init__(self, *args, **kwargs):
743
if "settings" in kwargs:
744
self.settings = kwargs["settings"]
745
del kwargs["settings"]
746
if "clients" in kwargs:
747
self.clients = kwargs["clients"]
748
del kwargs["clients"]
750
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1883
751
def server_bind(self):
1884
752
"""This overrides the normal server_bind() function
1885
753
to bind to an interface if one was specified, and also NOT to
1886
754
bind to an address or port if they were not specified."""
1887
if self.interface is not None:
1888
if SO_BINDTODEVICE is None:
1889
logger.error("SO_BINDTODEVICE does not exist;"
1890
" cannot bind to interface %s",
1894
self.socket.setsockopt(socket.SOL_SOCKET,
1898
except socket.error as error:
1899
if error[0] == errno.EPERM:
1900
logger.error("No permission to"
1901
" bind to interface %s",
1903
elif error[0] == errno.ENOPROTOOPT:
1904
logger.error("SO_BINDTODEVICE not available;"
1905
" cannot bind to interface %s",
755
if self.settings["interface"]:
756
# 25 is from /usr/include/asm-i486/socket.h
757
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
759
self.socket.setsockopt(socket.SOL_SOCKET,
761
self.settings["interface"])
762
except socket.error, error:
763
if error[0] == errno.EPERM:
764
logger.error(u"No permission to"
765
u" bind to interface %s",
766
self.settings["interface"])
1909
769
# Only bind(2) the socket if we really need to.
1910
770
if self.server_address[0] or self.server_address[1]:
1911
771
if not self.server_address[0]:
1912
if self.address_family == socket.AF_INET6:
1913
any_address = "::" # in6addr_any
1915
any_address = socket.INADDR_ANY
1916
self.server_address = (any_address,
773
self.server_address = (in6addr_any,
1917
774
self.server_address[1])
1918
775
elif not self.server_address[1]:
1919
776
self.server_address = (self.server_address[0],
1921
# if self.interface:
778
# if self.settings["interface"]:
1922
779
# self.server_address = (self.server_address[0],
1925
782
# if_nametoindex
1927
return socketserver.TCPServer.server_bind(self)
1930
class MandosServer(IPv6_TCPServer):
1934
clients: set of Client objects
1935
gnutls_priority GnuTLS priority string
1936
use_dbus: Boolean; to emit D-Bus signals or not
1938
Assumes a gobject.MainLoop event loop.
1940
def __init__(self, server_address, RequestHandlerClass,
1941
interface=None, use_ipv6=True, clients=None,
1942
gnutls_priority=None, use_dbus=True):
1943
self.enabled = False
1944
self.clients = clients
1945
if self.clients is None:
1947
self.use_dbus = use_dbus
1948
self.gnutls_priority = gnutls_priority
1949
IPv6_TCPServer.__init__(self, server_address,
1950
RequestHandlerClass,
1951
interface = interface,
1952
use_ipv6 = use_ipv6)
785
return super(IPv6_TCPServer, self).server_bind()
1953
786
def server_activate(self):
1954
787
if self.enabled:
1955
return socketserver.TCPServer.server_activate(self)
788
return super(IPv6_TCPServer, self).server_activate()
1957
789
def enable(self):
1958
790
self.enabled = True
1960
def add_pipe(self, parent_pipe, proc):
1961
# Call "handle_ipc" for both data and EOF events
1962
gobject.io_add_watch(parent_pipe.fileno(),
1963
gobject.IO_IN | gobject.IO_HUP,
1964
functools.partial(self.handle_ipc,
1969
def handle_ipc(self, source, condition, parent_pipe=None,
1970
proc = None, client_object=None):
1972
gobject.IO_IN: "IN", # There is data to read.
1973
gobject.IO_OUT: "OUT", # Data can be written (without
1975
gobject.IO_PRI: "PRI", # There is urgent data to read.
1976
gobject.IO_ERR: "ERR", # Error condition.
1977
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1978
# broken, usually for pipes and
1981
conditions_string = ' | '.join(name
1983
condition_names.iteritems()
1984
if cond & condition)
1985
# error, or the other end of multiprocessing.Pipe has closed
1986
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1987
# Wait for other process to exit
1991
# Read a request from the child
1992
request = parent_pipe.recv()
1993
command = request[0]
1995
if command == 'init':
1997
address = request[2]
1999
for c in self.clients.itervalues():
2000
if c.fingerprint == fpr:
2004
logger.info("Client not found for fingerprint: %s, ad"
2005
"dress: %s", fpr, address)
2008
mandos_dbus_service.ClientNotFound(fpr,
2010
parent_pipe.send(False)
2013
gobject.io_add_watch(parent_pipe.fileno(),
2014
gobject.IO_IN | gobject.IO_HUP,
2015
functools.partial(self.handle_ipc,
2021
parent_pipe.send(True)
2022
# remove the old hook in favor of the new above hook on
2025
if command == 'funcall':
2026
funcname = request[1]
2030
parent_pipe.send(('data', getattr(client_object,
2034
if command == 'getattr':
2035
attrname = request[1]
2036
if callable(client_object.__getattribute__(attrname)):
2037
parent_pipe.send(('function',))
2039
parent_pipe.send(('data', client_object
2040
.__getattribute__(attrname)))
2042
if command == 'setattr':
2043
attrname = request[1]
2045
setattr(client_object, attrname, value)
2050
793
def string_to_delta(interval):
2289
1038
(gnutls.library.functions
2290
1039
.gnutls_global_set_log_function(debug_gnutls))
2292
# Redirect stdin so all checkers get /dev/null
2293
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2294
os.dup2(null, sys.stdin.fileno())
2298
# Need to fork before connecting to D-Bus
2300
# Close all input and output, do double fork, etc.
2303
gobject.threads_init()
1042
service = AvahiService(name = server_settings["servicename"],
1043
servicetype = "_mandos._tcp", )
1044
if server_settings["interface"]:
1045
service.interface = (if_nametoindex
1046
(server_settings["interface"]))
2305
1048
global main_loop
2306
1051
# From the Avahi example code
2307
DBusGMainLoop(set_as_default=True)
1052
DBusGMainLoop(set_as_default=True )
2308
1053
main_loop = gobject.MainLoop()
2309
1054
bus = dbus.SystemBus()
1055
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1056
avahi.DBUS_PATH_SERVER),
1057
avahi.DBUS_INTERFACE_SERVER)
2310
1058
# End of Avahi example code
2313
bus_name = dbus.service.BusName("se.recompile.Mandos",
2314
bus, do_not_queue=True)
2315
old_bus_name = (dbus.service.BusName
2316
("se.bsnet.fukt.Mandos", bus,
2318
except dbus.exceptions.NameExistsException as e:
2319
logger.error(unicode(e) + ", disabling D-Bus")
2321
server_settings["use_dbus"] = False
2322
tcp_server.use_dbus = False
2323
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2324
service = AvahiServiceToSyslog(name =
2325
server_settings["servicename"],
2326
servicetype = "_mandos._tcp",
2327
protocol = protocol, bus = bus)
2328
if server_settings["interface"]:
2329
service.interface = (if_nametoindex
2330
(str(server_settings["interface"])))
2332
global multiprocessing_manager
2333
multiprocessing_manager = multiprocessing.Manager()
2335
client_class = Client
2337
client_class = functools.partial(ClientDBusTransitional,
2340
client_settings = Client.config_parser(client_config)
2341
old_client_settings = {}
2344
# Get client data and settings from last running state.
2345
if server_settings["restore"]:
2347
with open(stored_state_path, "rb") as stored_state:
2348
clients_data, old_client_settings = (pickle.load
2350
os.remove(stored_state_path)
2351
except IOError as e:
2352
logger.warning("Could not load persistent state: {0}"
2354
if e.errno != errno.ENOENT:
2356
except EOFError as e:
2357
logger.warning("Could not load persistent state: "
2358
"EOFError: {0}".format(e))
2360
with PGPEngine() as pgp:
2361
for client_name, client in clients_data.iteritems():
2362
# Decide which value to use after restoring saved state.
2363
# We have three different values: Old config file,
2364
# new config file, and saved state.
2365
# New config value takes precedence if it differs from old
2366
# config value, otherwise use saved state.
2367
for name, value in client_settings[client_name].items():
2369
# For each value in new config, check if it
2370
# differs from the old config value (Except for
2371
# the "secret" attribute)
2372
if (name != "secret" and
2373
value != old_client_settings[client_name]
2375
client[name] = value
2379
# Clients who has passed its expire date can still be
2380
# enabled if its last checker was successful. Clients
2381
# whose checker succeeded before we stored its state is
2382
# assumed to have successfully run all checkers during
2384
if client["enabled"]:
2385
if datetime.datetime.utcnow() >= client["expires"]:
2386
if not client["last_checked_ok"]:
2388
"disabling client {0} - Client never "
2389
"performed a successful checker"
2390
.format(client_name))
2391
client["enabled"] = False
2392
elif client["last_checker_status"] != 0:
2394
"disabling client {0} - Client "
2395
"last checker failed with error code {1}"
2396
.format(client_name,
2397
client["last_checker_status"]))
2398
client["enabled"] = False
2400
client["expires"] = (datetime.datetime
2402
+ client["timeout"])
2403
logger.debug("Last checker succeeded,"
2404
" keeping {0} enabled"
2405
.format(client_name))
2407
client["secret"] = (
2408
pgp.decrypt(client["encrypted_secret"],
2409
client_settings[client_name]
2412
# If decryption fails, we use secret from new settings
2413
logger.debug("Failed to decrypt {0} old secret"
2414
.format(client_name))
2415
client["secret"] = (
2416
client_settings[client_name]["secret"])
2418
# Add/remove clients based on new changes made to config
2419
for client_name in (set(old_client_settings)
2420
- set(client_settings)):
2421
del clients_data[client_name]
2422
for client_name in (set(client_settings)
2423
- set(old_client_settings)):
2424
clients_data[client_name] = client_settings[client_name]
2426
# Create all client objects
2427
for client_name, client in clients_data.iteritems():
2428
tcp_server.clients[client_name] = client_class(
2429
name = client_name, settings = client)
2431
if not tcp_server.clients:
2432
logger.warning("No clients defined")
1060
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1062
clients.update(Set(Client(name = section,
1064
= dict(client_config.items(section)),
1065
use_dbus = use_dbus)
1066
for section in client_config.sections()))
1068
logger.warning(u"No clients defined")
1071
# Redirect stdin so all checkers get /dev/null
1072
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1073
os.dup2(null, sys.stdin.fileno())
1077
# No console logging
1078
logger.removeHandler(console)
1079
# Close all input and output, do double fork, etc.
1084
pidfile.write(str(pid) + "\n")
1088
logger.error(u"Could not write to file %r with PID %d",
1091
# "pidfile" was never created
1096
"Cleanup function; run on exit"
1098
# From the Avahi example code
1099
if not group is None:
1102
# End of Avahi example code
1105
client = clients.pop()
1106
client.disable_hook = None
1109
atexit.register(cleanup)
2438
pidfile.write(str(pid) + "\n".encode("utf-8"))
2441
logger.error("Could not write to file %r with PID %d",
2444
# "pidfile" was never created
2447
1112
signal.signal(signal.SIGINT, signal.SIG_IGN)
2449
1113
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2450
1114
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2453
class MandosDBusService(DBusObjectWithProperties):
1117
class MandosServer(dbus.service.Object):
2454
1118
"""A D-Bus proxy object"""
2455
1119
def __init__(self):
2456
1120
dbus.service.Object.__init__(self, bus, "/")
2457
_interface = "se.recompile.Mandos"
2459
@dbus_interface_annotations(_interface)
2461
return { "org.freedesktop.DBus.Property"
2462
".EmitsChangedSignal":
2465
@dbus.service.signal(_interface, signature="o")
2466
def ClientAdded(self, objpath):
2470
@dbus.service.signal(_interface, signature="ss")
2471
def ClientNotFound(self, fingerprint, address):
1121
_interface = u"se.bsnet.fukt.Mandos"
1123
@dbus.service.signal(_interface, signature="oa{sv}")
1124
def ClientAdded(self, objpath, properties):