88
80
except ImportError:
89
81
SO_BINDTODEVICE = None
92
stored_state_file = "clients.pickle"
94
logger = logging.getLogger()
86
#logger = logging.getLogger(u'mandos')
87
logger = logging.Logger(u'mandos')
95
88
syslogger = (logging.handlers.SysLogHandler
96
89
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
97
address = str("/dev/log")))
100
if_nametoindex = (ctypes.cdll.LoadLibrary
101
(ctypes.util.find_library("c"))
103
except (OSError, AttributeError):
104
def if_nametoindex(interface):
105
"Get an interface index the hard way, i.e. using fcntl()"
106
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
107
with contextlib.closing(socket.socket()) as s:
108
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
109
struct.pack(str("16s16x"),
111
interface_index = struct.unpack(str("I"),
113
return interface_index
116
def initlogger(debug, level=logging.WARNING):
117
"""init logger and add loglevel"""
119
syslogger.setFormatter(logging.Formatter
120
('Mandos [%(process)d]: %(levelname)s:'
122
logger.addHandler(syslogger)
125
console = logging.StreamHandler()
126
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
130
logger.addHandler(console)
131
logger.setLevel(level)
134
class PGPError(Exception):
135
"""Exception if encryption/decryption fails"""
139
class PGPEngine(object):
140
"""A simple class for OpenPGP symmetric encryption & decryption"""
142
self.gnupg = GnuPGInterface.GnuPG()
143
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
144
self.gnupg = GnuPGInterface.GnuPG()
145
self.gnupg.options.meta_interactive = False
146
self.gnupg.options.homedir = self.tempdir
147
self.gnupg.options.extra_args.extend(['--force-mdc',
154
def __exit__ (self, exc_type, exc_value, traceback):
162
if self.tempdir is not None:
163
# Delete contents of tempdir
164
for root, dirs, files in os.walk(self.tempdir,
166
for filename in files:
167
os.remove(os.path.join(root, filename))
169
os.rmdir(os.path.join(root, dirname))
171
os.rmdir(self.tempdir)
174
def password_encode(self, password):
175
# Passphrase can not be empty and can not contain newlines or
176
# NUL bytes. So we prefix it and hex encode it.
177
return b"mandos" + binascii.hexlify(password)
179
def encrypt(self, data, password):
180
self.gnupg.passphrase = self.password_encode(password)
181
with open(os.devnull, "w") as devnull:
183
proc = self.gnupg.run(['--symmetric'],
184
create_fhs=['stdin', 'stdout'],
185
attach_fhs={'stderr': devnull})
186
with contextlib.closing(proc.handles['stdin']) as f:
188
with contextlib.closing(proc.handles['stdout']) as f:
189
ciphertext = f.read()
193
self.gnupg.passphrase = None
196
def decrypt(self, data, password):
197
self.gnupg.passphrase = self.password_encode(password)
198
with open(os.devnull, "w") as devnull:
200
proc = self.gnupg.run(['--decrypt'],
201
create_fhs=['stdin', 'stdout'],
202
attach_fhs={'stderr': devnull})
203
with contextlib.closing(proc.handles['stdin']) as f:
205
with contextlib.closing(proc.handles['stdout']) as f:
206
decrypted_plaintext = f.read()
210
self.gnupg.passphrase = None
211
return decrypted_plaintext
90
address = "/dev/log"))
91
syslogger.setFormatter(logging.Formatter
92
(u'Mandos [%(process)d]: %(levelname)s:'
94
logger.addHandler(syslogger)
96
console = logging.StreamHandler()
97
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
100
logger.addHandler(console)
214
102
class AvahiError(Exception):
215
103
def __init__(self, value, *args, **kwargs):
315
197
dbus.UInt16(self.port),
316
198
avahi.string_array_to_txt_array(self.TXT))
317
199
self.group.Commit()
319
200
def entry_group_state_changed(self, state, error):
320
201
"""Derived from the Avahi example code"""
321
logger.debug("Avahi entry group state change: %i", state)
202
logger.debug(u"Avahi entry group state change: %i", state)
323
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
324
logger.debug("Zeroconf service established.")
205
logger.debug(u"Zeroconf service established.")
325
206
elif state == avahi.ENTRY_GROUP_COLLISION:
326
logger.info("Zeroconf service name collision.")
207
logger.warning(u"Zeroconf service name collision.")
328
209
elif state == avahi.ENTRY_GROUP_FAILURE:
329
logger.critical("Avahi: Error in group state changed %s",
210
logger.critical(u"Avahi: Error in group state changed %s",
331
raise AvahiGroupError("State changed: {0!s}"
212
raise AvahiGroupError(u"State changed: %s"
334
214
def cleanup(self):
335
215
"""Derived from the Avahi example code"""
336
216
if self.group is not None:
339
except (dbus.exceptions.UnknownMethodException,
340
dbus.exceptions.DBusException):
342
218
self.group = None
345
def server_state_changed(self, state, error=None):
219
def server_state_changed(self, state):
346
220
"""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)
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
362
225
elif state == avahi.SERVER_RUNNING:
366
logger.debug("Unknown state: %r", state)
368
logger.debug("Unknown state: %r: %r", state, error)
370
227
def activate(self):
371
228
"""Derived from the Avahi example code"""
372
229
if self.server is None:
373
230
self.server = dbus.Interface(
374
231
self.bus.get_object(avahi.DBUS_NAME,
375
avahi.DBUS_PATH_SERVER,
376
follow_name_owner_changes=True),
232
avahi.DBUS_PATH_SERVER),
377
233
avahi.DBUS_INTERFACE_SERVER)
378
self.server.connect_to_signal("StateChanged",
234
self.server.connect_to_signal(u"StateChanged",
379
235
self.server_state_changed)
380
236
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))
401
239
class Client(object):
402
240
"""A representation of a client host served by this server.
405
approved: bool(); 'None' if not yet approved/disapproved
243
_approved: bool(); 'None' if not yet approved/disapproved
406
244
approval_delay: datetime.timedelta(); Time to wait for approval
407
245
approval_duration: datetime.timedelta(); Duration of one approval
408
246
checker: subprocess.Popen(); a running checker process used
426
263
interval: datetime.timedelta(); How often to start a new checker
427
264
last_approval_request: datetime.datetime(); (UTC) or None
428
265
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
266
last_enabled: datetime.datetime(); (UTC)
433
267
name: string; from the config file, used in log messages and
434
268
D-Bus identifiers
435
269
secret: bytestring; sent verbatim (over TLS) to client
436
270
timeout: datetime.timedelta(); How long from last_checked_ok
437
271
until this client is disabled
438
extended_timeout: extra long timeout when secret has been sent
439
272
runtime_expansions: Allowed attributes for runtime expansion.
440
expires: datetime.datetime(); time (UTC) when a client will be
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",
275
runtime_expansions = (u"approval_delay", u"approval_duration",
276
u"created", u"enabled", u"fingerprint",
277
u"host", u"interval", u"last_checked_ok",
278
u"last_enabled", u"name", u"timeout")
281
def _timedelta_to_milliseconds(td):
282
"Convert a datetime.timedelta() to milliseconds"
283
return ((td.days * 24 * 60 * 60 * 1000)
284
+ (td.seconds * 1000)
285
+ (td.microseconds // 1000))
459
287
def timeout_milliseconds(self):
460
288
"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)
289
return self._timedelta_to_milliseconds(self.timeout)
467
291
def interval_milliseconds(self):
468
292
"Return the 'interval' attribute in milliseconds"
469
return timedelta_to_milliseconds(self.interval)
293
return self._timedelta_to_milliseconds(self.interval)
471
295
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):
296
return self._timedelta_to_milliseconds(self.approval_delay)
298
def __init__(self, name = None, disable_hook=None, config=None):
299
"""Note: the 'checker' key in 'config' sets the
300
'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)
305
logger.debug(u"Creating client %r", self.name)
538
306
# Uppercase and remove spaces from fingerprint for later
539
307
# 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
309
self.fingerprint = (config[u"fingerprint"].upper()
311
logger.debug(u" Fingerprint: %s", self.fingerprint)
312
if u"secret" in config:
313
self.secret = config[u"secret"].decode(u"base64")
314
elif u"secfile" in config:
315
with open(os.path.expanduser(os.path.expandvars
316
(config[u"secfile"])),
318
self.secret = secfile.read()
320
raise TypeError(u"No secret or secfile for client %s"
322
self.host = config.get(u"host", u"")
323
self.created = datetime.datetime.utcnow()
325
self.last_approval_request = None
326
self.last_enabled = None
327
self.last_checked_ok = None
328
self.timeout = string_to_delta(config[u"timeout"])
329
self.interval = string_to_delta(config[u"interval"])
330
self.disable_hook = disable_hook
546
331
self.checker = None
547
332
self.checker_initiator_tag = None
548
333
self.disable_initiator_tag = None
549
334
self.checker_callback_tag = None
335
self.checker_command = config[u"checker"]
550
336
self.current_checker_command = None
337
self.last_connect = None
338
self._approved = None
339
self.approved_by_default = config.get(u"approved_by_default",
552
341
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)
342
self.approval_delay = string_to_delta(
343
config[u"approval_delay"])
344
self.approval_duration = string_to_delta(
345
config[u"approval_duration"])
346
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
568
# Send notice to process children that client state has changed
569
348
def send_changedstate(self):
570
with self.changedstate:
571
self.changedstate.notify_all()
349
self.changedstate.acquire()
350
self.changedstate.notify_all()
351
self.changedstate.release()
573
353
def enable(self):
574
354
"""Start this client's checker and timeout hooks"""
575
if getattr(self, "enabled", False):
355
if getattr(self, u"enabled", False):
576
356
# Already enabled
578
self.expires = datetime.datetime.utcnow() + self.timeout
358
self.send_changedstate()
580
359
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
360
# Schedule a new checker to be started an 'interval' from now,
609
361
# and every interval from then on.
610
if self.checker_initiator_tag is not None:
611
gobject.source_remove(self.checker_initiator_tag)
612
362
self.checker_initiator_tag = (gobject.timeout_add
613
363
(self.interval_milliseconds(),
614
364
self.start_checker))
615
365
# 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
366
self.disable_initiator_tag = (gobject.timeout_add
619
367
(self.timeout_milliseconds(),
621
370
# Also start a new checker *right now*.
622
371
self.start_checker()
373
def disable(self, quiet=True):
374
"""Disable this client."""
375
if not getattr(self, "enabled", False):
378
self.send_changedstate()
380
logger.info(u"Disabling client %s", self.name)
381
if getattr(self, u"disable_initiator_tag", False):
382
gobject.source_remove(self.disable_initiator_tag)
383
self.disable_initiator_tag = None
384
if getattr(self, u"checker_initiator_tag", False):
385
gobject.source_remove(self.checker_initiator_tag)
386
self.checker_initiator_tag = None
388
if self.disable_hook:
389
self.disable_hook(self)
391
# Do not run this again if called by a gobject.timeout_add
395
self.disable_hook = None
624
398
def checker_callback(self, pid, condition, command):
625
399
"""The checker has completed, so take appropriate actions."""
626
400
self.checker_callback_tag = None
627
401
self.checker = None
628
402
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",
403
exitstatus = os.WEXITSTATUS(condition)
405
logger.info(u"Checker for %(name)s succeeded",
633
407
self.checked_ok()
635
logger.info("Checker for %(name)s failed",
409
logger.info(u"Checker for %(name)s failed",
638
self.last_checker_status = -1
639
logger.warning("Checker for %(name)s crashed?",
412
logger.warning(u"Checker for %(name)s crashed?",
642
415
def checked_ok(self):
643
"""Assert that the client has been seen, alive and well."""
416
"""Bump up the timeout for this client.
418
This should only be called when the client has been seen,
644
421
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
422
gobject.source_remove(self.disable_initiator_tag)
423
self.disable_initiator_tag = (gobject.timeout_add
424
(self.timeout_milliseconds(),
661
427
def need_approval(self):
662
428
self.last_approval_request = datetime.datetime.utcnow()
844
572
class DBusObjectWithProperties(dbus.service.Object):
845
573
"""A D-Bus object with properties.
847
575
Classes inheriting from this can use the dbus_service_property
848
576
decorator to expose methods as D-Bus properties. It exposes the
849
577
standard Get(), Set(), and GetAll() methods on the D-Bus.
853
def _is_dbus_thing(thing):
854
"""Returns a function testing if an attribute is a D-Bus thing
856
If called like _is_dbus_thing("method") it returns a function
857
suitable for use as predicate to inspect.getmembers().
859
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
581
def _is_dbus_property(obj):
582
return getattr(obj, u"_dbus_is_property", False)
862
def _get_all_dbus_things(self, thing):
584
def _get_all_dbus_properties(self):
863
585
"""Returns a generator of (name, attribute) pairs
865
return ((getattr(athing.__get__(self), "_dbus_name",
867
athing.__get__(self))
868
for cls in self.__class__.__mro__
870
inspect.getmembers(cls,
871
self._is_dbus_thing(thing)))
587
return ((prop._dbus_name, prop)
589
inspect.getmembers(self, self._is_dbus_property))
873
591
def _get_dbus_property(self, interface_name, property_name):
874
592
"""Returns a bound method if one exists which is a D-Bus
875
593
property with the specified name and interface.
877
for cls in self.__class__.__mro__:
878
for name, value in (inspect.getmembers
880
self._is_dbus_thing("property"))):
881
if (value._dbus_name == property_name
882
and value._dbus_interface == interface_name):
883
return value.__get__(self)
595
for name in (property_name,
596
property_name + u"_dbus_property"):
597
prop = getattr(self, name, None)
599
or not self._is_dbus_property(prop)
600
or prop._dbus_name != property_name
601
or (interface_name and prop._dbus_interface
602
and interface_name != prop._dbus_interface)):
885
605
# No such property
886
raise DBusPropertyNotFound(self.dbus_object_path + ":"
887
+ interface_name + "."
606
raise DBusPropertyNotFound(self.dbus_object_path + u":"
607
+ interface_name + u"."
890
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
892
612
def Get(self, interface_name, property_name):
893
613
"""Standard D-Bus property Get() method, see D-Bus standard.
895
615
prop = self._get_dbus_property(interface_name, property_name)
896
if prop._dbus_access == "write":
616
if prop._dbus_access == u"write":
897
617
raise DBusPropertyAccessException(property_name)
899
if not hasattr(value, "variant_level"):
619
if not hasattr(value, u"variant_level"):
901
621
return type(value)(value, variant_level=value.variant_level+1)
903
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
623
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
904
624
def Set(self, interface_name, property_name, value):
905
625
"""Standard D-Bus property Set() method, see D-Bus standard.
907
627
prop = self._get_dbus_property(interface_name, property_name)
908
if prop._dbus_access == "read":
628
if prop._dbus_access == u"read":
909
629
raise DBusPropertyAccessException(property_name)
910
if prop._dbus_get_args_options["byte_arrays"]:
630
if prop._dbus_get_args_options[u"byte_arrays"]:
911
631
# The byte_arrays option is not supported yet on
912
632
# signatures other than "ay".
913
if prop._dbus_signature != "ay":
633
if prop._dbus_signature != u"ay":
915
value = dbus.ByteArray(b''.join(chr(byte)
635
value = dbus.ByteArray(''.join(unichr(byte)
919
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
920
out_signature="a{sv}")
639
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
640
out_signature=u"a{sv}")
921
641
def GetAll(self, interface_name):
922
642
"""Standard D-Bus property GetAll() method, see D-Bus
925
645
Note: Will not include properties with access="write".
928
for name, prop in self._get_all_dbus_things("property"):
648
for name, prop in self._get_all_dbus_properties():
929
649
if (interface_name
930
650
and interface_name != prop._dbus_interface):
931
651
# Interface non-empty but did not match
933
653
# Ignore write-only properties
934
if prop._dbus_access == "write":
654
if prop._dbus_access == u"write":
937
if not hasattr(value, "variant_level"):
938
properties[name] = value
657
if not hasattr(value, u"variant_level"):
940
properties[name] = type(value)(value, variant_level=
941
value.variant_level+1)
942
return dbus.Dictionary(properties, signature="sv")
660
all[name] = type(value)(value, variant_level=
661
value.variant_level+1)
662
return dbus.Dictionary(all, signature=u"sv")
944
664
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
946
666
path_keyword='object_path',
947
667
connection_keyword='connection')
948
668
def Introspect(self, object_path, connection):
949
"""Overloading of standard D-Bus method.
951
Inserts property tags and interface annotation tags.
669
"""Standard D-Bus method, overloaded to insert property tags.
953
671
xmlstring = dbus.service.Object.Introspect(self, object_path,
956
674
document = xml.dom.minidom.parseString(xmlstring)
957
675
def make_tag(document, name, prop):
958
e = document.createElement("property")
959
e.setAttribute("name", name)
960
e.setAttribute("type", prop._dbus_signature)
961
e.setAttribute("access", prop._dbus_access)
676
e = document.createElement(u"property")
677
e.setAttribute(u"name", name)
678
e.setAttribute(u"type", prop._dbus_signature)
679
e.setAttribute(u"access", prop._dbus_access)
963
for if_tag in document.getElementsByTagName("interface"):
681
for if_tag in document.getElementsByTagName(u"interface"):
965
682
for tag in (make_tag(document, name, prop)
967
in self._get_all_dbus_things("property")
684
in self._get_all_dbus_properties()
968
685
if prop._dbus_interface
969
== if_tag.getAttribute("name")):
686
== if_tag.getAttribute(u"name")):
970
687
if_tag.appendChild(tag)
971
# Add annotation tags
972
for typ in ("method", "signal", "property"):
973
for tag in if_tag.getElementsByTagName(typ):
975
for name, prop in (self.
976
_get_all_dbus_things(typ)):
977
if (name == tag.getAttribute("name")
978
and prop._dbus_interface
979
== if_tag.getAttribute("name")):
980
annots.update(getattr
984
for name, value in annots.iteritems():
985
ann_tag = document.createElement(
987
ann_tag.setAttribute("name", name)
988
ann_tag.setAttribute("value", value)
989
tag.appendChild(ann_tag)
990
# Add interface annotation tags
991
for annotation, value in dict(
992
itertools.chain.from_iterable(
993
annotations().iteritems()
994
for name, annotations in
995
self._get_all_dbus_things("interface")
996
if name == if_tag.getAttribute("name")
998
ann_tag = document.createElement("annotation")
999
ann_tag.setAttribute("name", annotation)
1000
ann_tag.setAttribute("value", value)
1001
if_tag.appendChild(ann_tag)
1002
688
# Add the names to the return values for the
1003
689
# "org.freedesktop.DBus.Properties" methods
1004
if (if_tag.getAttribute("name")
1005
== "org.freedesktop.DBus.Properties"):
1006
for cn in if_tag.getElementsByTagName("method"):
1007
if cn.getAttribute("name") == "Get":
1008
for arg in cn.getElementsByTagName("arg"):
1009
if (arg.getAttribute("direction")
1011
arg.setAttribute("name", "value")
1012
elif cn.getAttribute("name") == "GetAll":
1013
for arg in cn.getElementsByTagName("arg"):
1014
if (arg.getAttribute("direction")
1016
arg.setAttribute("name", "props")
1017
xmlstring = document.toxml("utf-8")
690
if (if_tag.getAttribute(u"name")
691
== u"org.freedesktop.DBus.Properties"):
692
for cn in if_tag.getElementsByTagName(u"method"):
693
if cn.getAttribute(u"name") == u"Get":
694
for arg in cn.getElementsByTagName(u"arg"):
695
if (arg.getAttribute(u"direction")
697
arg.setAttribute(u"name", u"value")
698
elif cn.getAttribute(u"name") == u"GetAll":
699
for arg in cn.getElementsByTagName(u"arg"):
700
if (arg.getAttribute(u"direction")
702
arg.setAttribute(u"name", u"props")
703
xmlstring = document.toxml(u"utf-8")
1018
704
document.unlink()
1019
705
except (AttributeError, xml.dom.DOMException,
1020
xml.parsers.expat.ExpatError) as error:
1021
logger.error("Failed to override Introspection method",
706
xml.parsers.expat.ExpatError), error:
707
logger.error(u"Failed to override Introspection method",
1023
709
return xmlstring
1026
def datetime_to_dbus (dt, variant_level=0):
1027
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1029
return dbus.String("", variant_level = variant_level)
1030
return dbus.String(dt.isoformat(),
1031
variant_level=variant_level)
1034
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1035
"""A class decorator; applied to a subclass of
1036
dbus.service.Object, it will add alternate D-Bus attributes with
1037
interface names according to the "alt_interface_names" mapping.
1040
@alternate_dbus_names({"org.example.Interface":
1041
"net.example.AlternateInterface"})
1042
class SampleDBusObject(dbus.service.Object):
1043
@dbus.service.method("org.example.Interface")
1044
def SampleDBusMethod():
1047
The above "SampleDBusMethod" on "SampleDBusObject" will be
1048
reachable via two interfaces: "org.example.Interface" and
1049
"net.example.AlternateInterface", the latter of which will have
1050
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1051
"true", unless "deprecate" is passed with a False value.
1053
This works for methods and signals, and also for D-Bus properties
1054
(from DBusObjectWithProperties) and interfaces (from the
1055
dbus_interface_annotations decorator).
1058
for orig_interface_name, alt_interface_name in (
1059
alt_interface_names.iteritems()):
1061
interface_names = set()
1062
# Go though all attributes of the class
1063
for attrname, attribute in inspect.getmembers(cls):
1064
# Ignore non-D-Bus attributes, and D-Bus attributes
1065
# with the wrong interface name
1066
if (not hasattr(attribute, "_dbus_interface")
1067
or not attribute._dbus_interface
1068
.startswith(orig_interface_name)):
1070
# Create an alternate D-Bus interface name based on
1072
alt_interface = (attribute._dbus_interface
1073
.replace(orig_interface_name,
1074
alt_interface_name))
1075
interface_names.add(alt_interface)
1076
# Is this a D-Bus signal?
1077
if getattr(attribute, "_dbus_is_signal", False):
1078
# Extract the original non-method function by
1080
nonmethod_func = (dict(
1081
zip(attribute.func_code.co_freevars,
1082
attribute.__closure__))["func"]
1084
# Create a new, but exactly alike, function
1085
# object, and decorate it to be a new D-Bus signal
1086
# with the alternate D-Bus interface name
1087
new_function = (dbus.service.signal
1089
attribute._dbus_signature)
1090
(types.FunctionType(
1091
nonmethod_func.func_code,
1092
nonmethod_func.func_globals,
1093
nonmethod_func.func_name,
1094
nonmethod_func.func_defaults,
1095
nonmethod_func.func_closure)))
1096
# Copy annotations, if any
1098
new_function._dbus_annotations = (
1099
dict(attribute._dbus_annotations))
1100
except AttributeError:
1102
# Define a creator of a function to call both the
1103
# original and alternate functions, so both the
1104
# original and alternate signals gets sent when
1105
# the function is called
1106
def fixscope(func1, func2):
1107
"""This function is a scope container to pass
1108
func1 and func2 to the "call_both" function
1109
outside of its arguments"""
1110
def call_both(*args, **kwargs):
1111
"""This function will emit two D-Bus
1112
signals by calling func1 and func2"""
1113
func1(*args, **kwargs)
1114
func2(*args, **kwargs)
1116
# Create the "call_both" function and add it to
1118
attr[attrname] = fixscope(attribute, new_function)
1119
# Is this a D-Bus method?
1120
elif getattr(attribute, "_dbus_is_method", False):
1121
# Create a new, but exactly alike, function
1122
# object. Decorate it to be a new D-Bus method
1123
# with the alternate D-Bus interface name. Add it
1125
attr[attrname] = (dbus.service.method
1127
attribute._dbus_in_signature,
1128
attribute._dbus_out_signature)
1130
(attribute.func_code,
1131
attribute.func_globals,
1132
attribute.func_name,
1133
attribute.func_defaults,
1134
attribute.func_closure)))
1135
# Copy annotations, if any
1137
attr[attrname]._dbus_annotations = (
1138
dict(attribute._dbus_annotations))
1139
except AttributeError:
1141
# Is this a D-Bus property?
1142
elif getattr(attribute, "_dbus_is_property", False):
1143
# Create a new, but exactly alike, function
1144
# object, and decorate it to be a new D-Bus
1145
# property with the alternate D-Bus interface
1146
# name. Add it to the class.
1147
attr[attrname] = (dbus_service_property
1149
attribute._dbus_signature,
1150
attribute._dbus_access,
1152
._dbus_get_args_options
1155
(attribute.func_code,
1156
attribute.func_globals,
1157
attribute.func_name,
1158
attribute.func_defaults,
1159
attribute.func_closure)))
1160
# Copy annotations, if any
1162
attr[attrname]._dbus_annotations = (
1163
dict(attribute._dbus_annotations))
1164
except AttributeError:
1166
# Is this a D-Bus interface?
1167
elif getattr(attribute, "_dbus_is_interface", False):
1168
# Create a new, but exactly alike, function
1169
# object. Decorate it to be a new D-Bus interface
1170
# with the alternate D-Bus interface name. Add it
1172
attr[attrname] = (dbus_interface_annotations
1175
(attribute.func_code,
1176
attribute.func_globals,
1177
attribute.func_name,
1178
attribute.func_defaults,
1179
attribute.func_closure)))
1181
# Deprecate all alternate interfaces
1182
iname="_AlternateDBusNames_interface_annotation{0}"
1183
for interface_name in interface_names:
1184
@dbus_interface_annotations(interface_name)
1186
return { "org.freedesktop.DBus.Deprecated":
1188
# Find an unused name
1189
for aname in (iname.format(i)
1190
for i in itertools.count()):
1191
if aname not in attr:
1195
# Replace the class with a new subclass of it with
1196
# methods, signals, etc. as created above.
1197
cls = type(b"{0}Alternate".format(cls.__name__),
1203
@alternate_dbus_interfaces({"se.recompile.Mandos":
1204
"se.bsnet.fukt.Mandos"})
1205
712
class ClientDBus(Client, DBusObjectWithProperties):
1206
713
"""A Client class using D-Bus
1213
720
runtime_expansions = (Client.runtime_expansions
1214
+ ("dbus_object_path",))
721
+ (u"dbus_object_path",))
1216
723
# dbus.service.Object doesn't use super(), so we can't either.
1218
725
def __init__(self, bus = None, *args, **kwargs):
726
self._approvals_pending = 0
1220
728
Client.__init__(self, *args, **kwargs)
1221
729
# Only now, when this client is initialized, can it show up on
1223
731
client_object_name = unicode(self.name).translate(
1224
{ord("."): ord("_"),
1225
ord("-"): ord("_")})
732
{ord(u"."): ord(u"_"),
733
ord(u"-"): ord(u"_")})
1226
734
self.dbus_object_path = (dbus.ObjectPath
1227
("/clients/" + client_object_name))
735
(u"/clients/" + client_object_name))
1228
736
DBusObjectWithProperties.__init__(self, self.bus,
1229
737
self.dbus_object_path)
1231
def notifychangeproperty(transform_func,
1232
dbus_name, type_func=lambda x: x,
1234
""" Modify a variable so that it's a property which announces
1235
its changes to DBus.
1237
transform_fun: Function that takes a value and a variant_level
1238
and transforms it to a D-Bus type.
1239
dbus_name: D-Bus name of the variable
1240
type_func: Function that transform the value before sending it
1241
to the D-Bus. Default: no transform
1242
variant_level: D-Bus variant level. Default: 1
1244
attrname = "_{0}".format(dbus_name)
1245
def setter(self, value):
1246
if hasattr(self, "dbus_object_path"):
1247
if (not hasattr(self, attrname) or
1248
type_func(getattr(self, attrname, None))
1249
!= type_func(value)):
1250
dbus_value = transform_func(type_func(value),
1253
self.PropertyChanged(dbus.String(dbus_name),
1255
setattr(self, attrname, value)
1257
return property(lambda self: getattr(self, attrname), setter)
1259
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1260
approvals_pending = notifychangeproperty(dbus.Boolean,
1263
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1264
last_enabled = notifychangeproperty(datetime_to_dbus,
1266
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1267
type_func = lambda checker:
1268
checker is not None)
1269
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1271
last_checker_status = notifychangeproperty(dbus.Int16,
1272
"LastCheckerStatus")
1273
last_approval_request = notifychangeproperty(
1274
datetime_to_dbus, "LastApprovalRequest")
1275
approved_by_default = notifychangeproperty(dbus.Boolean,
1276
"ApprovedByDefault")
1277
approval_delay = notifychangeproperty(dbus.UInt64,
1280
timedelta_to_milliseconds)
1281
approval_duration = notifychangeproperty(
1282
dbus.UInt64, "ApprovalDuration",
1283
type_func = timedelta_to_milliseconds)
1284
host = notifychangeproperty(dbus.String, "Host")
1285
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1287
timedelta_to_milliseconds)
1288
extended_timeout = notifychangeproperty(
1289
dbus.UInt64, "ExtendedTimeout",
1290
type_func = timedelta_to_milliseconds)
1291
interval = notifychangeproperty(dbus.UInt64,
1294
timedelta_to_milliseconds)
1295
checker_command = notifychangeproperty(dbus.String, "Checker")
1297
del notifychangeproperty
739
def _get_approvals_pending(self):
740
return self._approvals_pending
741
def _set_approvals_pending(self, value):
742
old_value = self._approvals_pending
743
self._approvals_pending = value
745
if (hasattr(self, "dbus_object_path")
746
and bval is not bool(old_value)):
747
dbus_bool = dbus.Boolean(bval, variant_level=1)
748
self.PropertyChanged(dbus.String(u"ApprovalPending"),
751
approvals_pending = property(_get_approvals_pending,
752
_set_approvals_pending)
753
del _get_approvals_pending, _set_approvals_pending
756
def _datetime_to_dbus(dt, variant_level=0):
757
"""Convert a UTC datetime.datetime() to a D-Bus type."""
758
return dbus.String(dt.isoformat(),
759
variant_level=variant_level)
762
oldstate = getattr(self, u"enabled", False)
763
r = Client.enable(self)
764
if oldstate != self.enabled:
766
self.PropertyChanged(dbus.String(u"Enabled"),
767
dbus.Boolean(True, variant_level=1))
768
self.PropertyChanged(
769
dbus.String(u"LastEnabled"),
770
self._datetime_to_dbus(self.last_enabled,
774
def disable(self, quiet = False):
775
oldstate = getattr(self, u"enabled", False)
776
r = Client.disable(self, quiet=quiet)
777
if not quiet and oldstate != self.enabled:
779
self.PropertyChanged(dbus.String(u"Enabled"),
780
dbus.Boolean(False, variant_level=1))
1299
783
def __del__(self, *args, **kwargs):
1301
785
self.remove_from_connection()
1302
786
except LookupError:
1304
if hasattr(DBusObjectWithProperties, "__del__"):
788
if hasattr(DBusObjectWithProperties, u"__del__"):
1305
789
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1306
790
Client.__del__(self, *args, **kwargs)
1440
951
# ApprovalPending - property
1441
@dbus_service_property(_interface, signature="b", access="read")
952
@dbus_service_property(_interface, signature=u"b", access=u"read")
1442
953
def ApprovalPending_dbus_property(self):
1443
954
return dbus.Boolean(bool(self.approvals_pending))
1445
956
# ApprovedByDefault - property
1446
@dbus_service_property(_interface, signature="b",
957
@dbus_service_property(_interface, signature=u"b",
1448
959
def ApprovedByDefault_dbus_property(self, value=None):
1449
960
if value is None: # get
1450
961
return dbus.Boolean(self.approved_by_default)
1451
962
self.approved_by_default = bool(value)
964
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
965
dbus.Boolean(value, variant_level=1))
1453
967
# ApprovalDelay - property
1454
@dbus_service_property(_interface, signature="t",
968
@dbus_service_property(_interface, signature=u"t",
1456
970
def ApprovalDelay_dbus_property(self, value=None):
1457
971
if value is None: # get
1458
972
return dbus.UInt64(self.approval_delay_milliseconds())
1459
973
self.approval_delay = datetime.timedelta(0, 0, 0, value)
975
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
976
dbus.UInt64(value, variant_level=1))
1461
978
# ApprovalDuration - property
1462
@dbus_service_property(_interface, signature="t",
979
@dbus_service_property(_interface, signature=u"t",
1464
981
def ApprovalDuration_dbus_property(self, value=None):
1465
982
if value is None: # get
1466
return dbus.UInt64(timedelta_to_milliseconds(
983
return dbus.UInt64(self._timedelta_to_milliseconds(
1467
984
self.approval_duration))
1468
985
self.approval_duration = datetime.timedelta(0, 0, 0, value)
987
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
988
dbus.UInt64(value, variant_level=1))
1470
990
# Name - property
1471
@dbus_service_property(_interface, signature="s", access="read")
991
@dbus_service_property(_interface, signature=u"s", access=u"read")
1472
992
def Name_dbus_property(self):
1473
993
return dbus.String(self.name)
1475
995
# Fingerprint - property
1476
@dbus_service_property(_interface, signature="s", access="read")
996
@dbus_service_property(_interface, signature=u"s", access=u"read")
1477
997
def Fingerprint_dbus_property(self):
1478
998
return dbus.String(self.fingerprint)
1480
1000
# Host - property
1481
@dbus_service_property(_interface, signature="s",
1001
@dbus_service_property(_interface, signature=u"s",
1002
access=u"readwrite")
1483
1003
def Host_dbus_property(self, value=None):
1484
1004
if value is None: # get
1485
1005
return dbus.String(self.host)
1486
self.host = unicode(value)
1008
self.PropertyChanged(dbus.String(u"Host"),
1009
dbus.String(value, variant_level=1))
1488
1011
# Created - property
1489
@dbus_service_property(_interface, signature="s", access="read")
1012
@dbus_service_property(_interface, signature=u"s", access=u"read")
1490
1013
def Created_dbus_property(self):
1491
return datetime_to_dbus(self.created)
1014
return dbus.String(self._datetime_to_dbus(self.created))
1493
1016
# LastEnabled - property
1494
@dbus_service_property(_interface, signature="s", access="read")
1017
@dbus_service_property(_interface, signature=u"s", access=u"read")
1495
1018
def LastEnabled_dbus_property(self):
1496
return datetime_to_dbus(self.last_enabled)
1019
if self.last_enabled is None:
1020
return dbus.String(u"")
1021
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1498
1023
# Enabled - property
1499
@dbus_service_property(_interface, signature="b",
1024
@dbus_service_property(_interface, signature=u"b",
1025
access=u"readwrite")
1501
1026
def Enabled_dbus_property(self, value=None):
1502
1027
if value is None: # get
1503
1028
return dbus.Boolean(self.enabled)
1509
1034
# LastCheckedOK - property
1510
@dbus_service_property(_interface, signature="s",
1035
@dbus_service_property(_interface, signature=u"s",
1036
access=u"readwrite")
1512
1037
def LastCheckedOK_dbus_property(self, value=None):
1513
1038
if value is not None:
1514
1039
self.checked_ok()
1516
return datetime_to_dbus(self.last_checked_ok)
1518
# LastCheckerStatus - property
1519
@dbus_service_property(_interface, signature="n",
1521
def LastCheckerStatus_dbus_property(self):
1522
return dbus.Int16(self.last_checker_status)
1524
# Expires - property
1525
@dbus_service_property(_interface, signature="s", access="read")
1526
def Expires_dbus_property(self):
1527
return datetime_to_dbus(self.expires)
1041
if self.last_checked_ok is None:
1042
return dbus.String(u"")
1043
return dbus.String(self._datetime_to_dbus(self
1529
1046
# LastApprovalRequest - property
1530
@dbus_service_property(_interface, signature="s", access="read")
1047
@dbus_service_property(_interface, signature=u"s", access=u"read")
1531
1048
def LastApprovalRequest_dbus_property(self):
1532
return datetime_to_dbus(self.last_approval_request)
1049
if self.last_approval_request is None:
1050
return dbus.String(u"")
1051
return dbus.String(self.
1052
_datetime_to_dbus(self
1053
.last_approval_request))
1534
1055
# Timeout - property
1535
@dbus_service_property(_interface, signature="t",
1056
@dbus_service_property(_interface, signature=u"t",
1057
access=u"readwrite")
1537
1058
def Timeout_dbus_property(self, value=None):
1538
1059
if value is None: # get
1539
1060
return dbus.UInt64(self.timeout_milliseconds())
1540
old_timeout = self.timeout
1541
1061
self.timeout = datetime.timedelta(0, 0, 0, value)
1542
# Reschedule disabling
1544
now = datetime.datetime.utcnow()
1545
self.expires += self.timeout - old_timeout
1546
if self.expires <= now:
1547
# The timeout has passed
1550
if (getattr(self, "disable_initiator_tag", None)
1553
gobject.source_remove(self.disable_initiator_tag)
1554
self.disable_initiator_tag = (
1555
gobject.timeout_add(
1556
timedelta_to_milliseconds(self.expires - now),
1559
# ExtendedTimeout - property
1560
@dbus_service_property(_interface, signature="t",
1562
def ExtendedTimeout_dbus_property(self, value=None):
1563
if value is None: # get
1564
return dbus.UInt64(self.extended_timeout_milliseconds())
1565
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1063
self.PropertyChanged(dbus.String(u"Timeout"),
1064
dbus.UInt64(value, variant_level=1))
1065
if getattr(self, u"disable_initiator_tag", None) is None:
1067
# Reschedule timeout
1068
gobject.source_remove(self.disable_initiator_tag)
1069
self.disable_initiator_tag = None
1070
time_to_die = (self.
1071
_timedelta_to_milliseconds((self
1076
if time_to_die <= 0:
1077
# The timeout has passed
1080
self.disable_initiator_tag = (gobject.timeout_add
1081
(time_to_die, self.disable))
1567
1083
# Interval - property
1568
@dbus_service_property(_interface, signature="t",
1084
@dbus_service_property(_interface, signature=u"t",
1085
access=u"readwrite")
1570
1086
def Interval_dbus_property(self, value=None):
1571
1087
if value is None: # get
1572
1088
return dbus.UInt64(self.interval_milliseconds())
1573
1089
self.interval = datetime.timedelta(0, 0, 0, value)
1574
if getattr(self, "checker_initiator_tag", None) is None:
1091
self.PropertyChanged(dbus.String(u"Interval"),
1092
dbus.UInt64(value, variant_level=1))
1093
if getattr(self, u"checker_initiator_tag", None) is None:
1577
# Reschedule checker run
1578
gobject.source_remove(self.checker_initiator_tag)
1579
self.checker_initiator_tag = (gobject.timeout_add
1580
(value, self.start_checker))
1581
self.start_checker() # Start one now, too
1095
# Reschedule checker run
1096
gobject.source_remove(self.checker_initiator_tag)
1097
self.checker_initiator_tag = (gobject.timeout_add
1098
(value, self.start_checker))
1099
self.start_checker() # Start one now, too
1583
1101
# Checker - property
1584
@dbus_service_property(_interface, signature="s",
1102
@dbus_service_property(_interface, signature=u"s",
1103
access=u"readwrite")
1586
1104
def Checker_dbus_property(self, value=None):
1587
1105
if value is None: # get
1588
1106
return dbus.String(self.checker_command)
1589
self.checker_command = unicode(value)
1107
self.checker_command = value
1109
self.PropertyChanged(dbus.String(u"Checker"),
1110
dbus.String(self.checker_command,
1591
1113
# CheckerRunning - property
1592
@dbus_service_property(_interface, signature="b",
1114
@dbus_service_property(_interface, signature=u"b",
1115
access=u"readwrite")
1594
1116
def CheckerRunning_dbus_property(self, value=None):
1595
1117
if value is None: # get
1596
1118
return dbus.Boolean(self.checker is not None)
1648
1170
def handle(self):
1649
1171
with contextlib.closing(self.server.child_pipe) as child_pipe:
1650
logger.info("TCP connection from: %s",
1172
logger.info(u"TCP connection from: %s",
1651
1173
unicode(self.client_address))
1652
logger.debug("Pipe FD: %d",
1174
logger.debug(u"Pipe FD: %d",
1653
1175
self.server.child_pipe.fileno())
1655
1177
session = (gnutls.connection
1656
1178
.ClientSession(self.request,
1657
1179
gnutls.connection
1658
1180
.X509Credentials()))
1660
1182
# Note: gnutls.connection.X509Credentials is really a
1661
1183
# generic GnuTLS certificate credentials object so long as
1662
1184
# no X.509 keys are added to it. Therefore, we can use it
1663
1185
# here despite using OpenPGP certificates.
1665
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1666
# "+AES-256-CBC", "+SHA1",
1667
# "+COMP-NULL", "+CTYPE-OPENPGP",
1187
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1188
# u"+AES-256-CBC", u"+SHA1",
1189
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1669
1191
# Use a fallback default, since this MUST be set.
1670
1192
priority = self.server.gnutls_priority
1671
1193
if priority is None:
1194
priority = u"NORMAL"
1673
1195
(gnutls.library.functions
1674
1196
.gnutls_priority_set_direct(session._c_object,
1675
1197
priority, None))
1677
1199
# Start communication using the Mandos protocol
1678
1200
# Get protocol number
1679
1201
line = self.request.makefile().readline()
1680
logger.debug("Protocol version: %r", line)
1202
logger.debug(u"Protocol version: %r", line)
1682
1204
if int(line.strip().split()[0]) > 1:
1683
1205
raise RuntimeError
1684
except (ValueError, IndexError, RuntimeError) as error:
1685
logger.error("Unknown protocol version: %s", error)
1206
except (ValueError, IndexError, RuntimeError), error:
1207
logger.error(u"Unknown protocol version: %s", error)
1688
1210
# Start GnuTLS connection
1690
1212
session.handshake()
1691
except gnutls.errors.GNUTLSError as error:
1692
logger.warning("Handshake failed: %s", error)
1213
except gnutls.errors.GNUTLSError, error:
1214
logger.warning(u"Handshake failed: %s", error)
1693
1215
# Do not run session.bye() here: the session is not
1694
1216
# established. Just abandon the request.
1696
logger.debug("Handshake succeeded")
1218
logger.debug(u"Handshake succeeded")
1698
1220
approval_required = False
1701
1223
fpr = self.fingerprint(self.peer_certificate
1704
gnutls.errors.GNUTLSError) as error:
1705
logger.warning("Bad certificate: %s", error)
1225
except (TypeError, gnutls.errors.GNUTLSError), error:
1226
logger.warning(u"Bad certificate: %s", error)
1707
logger.debug("Fingerprint: %s", fpr)
1228
logger.debug(u"Fingerprint: %s", fpr)
1710
1231
client = ProxyClient(child_pipe, fpr,
1711
1232
self.client_address)
2139
1672
##################################################################
2140
1673
# Parsing of options, both command line and config file
2142
parser = argparse.ArgumentParser()
2143
parser.add_argument("-v", "--version", action="version",
2144
version = "%(prog)s {0}".format(version),
2145
help="show version number and exit")
2146
parser.add_argument("-i", "--interface", metavar="IF",
2147
help="Bind to interface IF")
2148
parser.add_argument("-a", "--address",
2149
help="Address to listen for requests on")
2150
parser.add_argument("-p", "--port", type=int,
2151
help="Port number to receive requests on")
2152
parser.add_argument("--check", action="store_true",
2153
help="Run self-test")
2154
parser.add_argument("--debug", action="store_true",
2155
help="Debug mode; run in foreground and log"
2157
parser.add_argument("--debuglevel", metavar="LEVEL",
2158
help="Debug level for stdout output")
2159
parser.add_argument("--priority", help="GnuTLS"
2160
" priority string (see GnuTLS documentation)")
2161
parser.add_argument("--servicename",
2162
metavar="NAME", help="Zeroconf service name")
2163
parser.add_argument("--configdir",
2164
default="/etc/mandos", metavar="DIR",
2165
help="Directory to search for configuration"
2167
parser.add_argument("--no-dbus", action="store_false",
2168
dest="use_dbus", help="Do not provide D-Bus"
2169
" system bus interface")
2170
parser.add_argument("--no-ipv6", action="store_false",
2171
dest="use_ipv6", help="Do not use IPv6")
2172
parser.add_argument("--no-restore", action="store_false",
2173
dest="restore", help="Do not restore stored"
2175
parser.add_argument("--statedir", metavar="DIR",
2176
help="Directory to save/restore state in")
2178
options = parser.parse_args()
1675
parser = optparse.OptionParser(version = "%%prog %s" % version)
1676
parser.add_option("-i", u"--interface", type=u"string",
1677
metavar="IF", help=u"Bind to interface IF")
1678
parser.add_option("-a", u"--address", type=u"string",
1679
help=u"Address to listen for requests on")
1680
parser.add_option("-p", u"--port", type=u"int",
1681
help=u"Port number to receive requests on")
1682
parser.add_option("--check", action=u"store_true",
1683
help=u"Run self-test")
1684
parser.add_option("--debug", action=u"store_true",
1685
help=u"Debug mode; run in foreground and log to"
1687
parser.add_option("--debuglevel", type=u"string", metavar="Level",
1688
help=u"Debug level for stdout output")
1689
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1690
u" priority string (see GnuTLS documentation)")
1691
parser.add_option("--servicename", type=u"string",
1692
metavar=u"NAME", help=u"Zeroconf service name")
1693
parser.add_option("--configdir", type=u"string",
1694
default=u"/etc/mandos", metavar=u"DIR",
1695
help=u"Directory to search for configuration"
1697
parser.add_option("--no-dbus", action=u"store_false",
1698
dest=u"use_dbus", help=u"Do not provide D-Bus"
1699
u" system bus interface")
1700
parser.add_option("--no-ipv6", action=u"store_false",
1701
dest=u"use_ipv6", help=u"Do not use IPv6")
1702
options = parser.parse_args()[0]
2180
1704
if options.check:
2232
1753
##################################################################
2234
1755
# For convenience
2235
debug = server_settings["debug"]
2236
debuglevel = server_settings["debuglevel"]
2237
use_dbus = server_settings["use_dbus"]
2238
use_ipv6 = server_settings["use_ipv6"]
2239
stored_state_path = os.path.join(server_settings["statedir"],
2243
initlogger(debug, logging.DEBUG)
2248
level = getattr(logging, debuglevel.upper())
2249
initlogger(debug, level)
2251
if server_settings["servicename"] != "Mandos":
1756
debug = server_settings[u"debug"]
1757
debuglevel = server_settings[u"debuglevel"]
1758
use_dbus = server_settings[u"use_dbus"]
1759
use_ipv6 = server_settings[u"use_ipv6"]
1761
if server_settings[u"servicename"] != u"Mandos":
2252
1762
syslogger.setFormatter(logging.Formatter
2253
('Mandos ({0}) [%(process)d]:'
2254
' %(levelname)s: %(message)s'
2255
.format(server_settings
1763
(u'Mandos (%s) [%%(process)d]:'
1764
u' %%(levelname)s: %%(message)s'
1765
% server_settings[u"servicename"]))
2258
1767
# Parse config file with clients
2259
client_config = configparser.SafeConfigParser(Client
2261
client_config.read(os.path.join(server_settings["configdir"],
1768
client_defaults = { u"timeout": u"1h",
1770
u"checker": u"fping -q -- %%(host)s",
1772
u"approval_delay": u"0s",
1773
u"approval_duration": u"1s",
1775
client_config = configparser.SafeConfigParser(client_defaults)
1776
client_config.read(os.path.join(server_settings[u"configdir"],
2264
1779
global mandos_dbus_service
2265
1780
mandos_dbus_service = None
2267
tcp_server = MandosServer((server_settings["address"],
2268
server_settings["port"]),
1782
tcp_server = MandosServer((server_settings[u"address"],
1783
server_settings[u"port"]),
2270
interface=(server_settings["interface"]
1785
interface=(server_settings[u"interface"]
2272
1787
use_ipv6=use_ipv6,
2273
1788
gnutls_priority=
2274
server_settings["priority"],
1789
server_settings[u"priority"],
2275
1790
use_dbus=use_dbus)
2277
pidfilename = "/var/run/mandos.pid"
1792
pidfilename = u"/var/run/mandos.pid"
2279
pidfile = open(pidfilename, "w")
2280
except IOError as e:
2281
logger.error("Could not open file %r", pidfilename,
1794
pidfile = open(pidfilename, u"w")
1796
logger.error(u"Could not open file %r", pidfilename)
2284
for name in ("_mandos", "mandos", "nobody"):
1799
uid = pwd.getpwnam(u"_mandos").pw_uid
1800
gid = pwd.getpwnam(u"_mandos").pw_gid
2286
uid = pwd.getpwnam(name).pw_uid
2287
gid = pwd.getpwnam(name).pw_gid
1803
uid = pwd.getpwnam(u"mandos").pw_uid
1804
gid = pwd.getpwnam(u"mandos").pw_gid
2289
1805
except KeyError:
1807
uid = pwd.getpwnam(u"nobody").pw_uid
1808
gid = pwd.getpwnam(u"nobody").pw_gid
2297
except OSError as error:
1815
except OSError, error:
2298
1816
if error[0] != errno.EPERM:
1819
if not debug and not debuglevel:
1820
syslogger.setLevel(logging.WARNING)
1821
console.setLevel(logging.WARNING)
1823
level = getattr(logging, debuglevel.upper())
1824
syslogger.setLevel(level)
1825
console.setLevel(level)
2302
1828
# Enable all possible GnuTLS debugging
2308
1834
@gnutls.library.types.gnutls_log_func
2309
1835
def debug_gnutls(level, string):
2310
logger.debug("GnuTLS: %s", string[:-1])
1836
logger.debug(u"GnuTLS: %s", string[:-1])
2312
1838
(gnutls.library.functions
2313
1839
.gnutls_global_set_log_function(debug_gnutls))
2315
1841
# Redirect stdin so all checkers get /dev/null
2316
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1842
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2317
1843
os.dup2(null, sys.stdin.fileno())
2321
# Need to fork before connecting to D-Bus
2323
# Close all input and output, do double fork, etc.
2326
gobject.threads_init()
1847
# No console logging
1848
logger.removeHandler(console)
2328
1851
global main_loop
2329
1852
# From the Avahi example code
2330
DBusGMainLoop(set_as_default=True)
1853
DBusGMainLoop(set_as_default=True )
2331
1854
main_loop = gobject.MainLoop()
2332
1855
bus = dbus.SystemBus()
2333
1856
# End of Avahi example code
2336
bus_name = dbus.service.BusName("se.recompile.Mandos",
1859
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2337
1860
bus, do_not_queue=True)
2338
old_bus_name = (dbus.service.BusName
2339
("se.bsnet.fukt.Mandos", bus,
2341
except dbus.exceptions.NameExistsException as e:
2342
logger.error("Disabling D-Bus:", exc_info=e)
1861
except dbus.exceptions.NameExistsException, e:
1862
logger.error(unicode(e) + u", disabling D-Bus")
2343
1863
use_dbus = False
2344
server_settings["use_dbus"] = False
1864
server_settings[u"use_dbus"] = False
2345
1865
tcp_server.use_dbus = False
2346
1866
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2347
service = AvahiServiceToSyslog(name =
2348
server_settings["servicename"],
2349
servicetype = "_mandos._tcp",
2350
protocol = protocol, bus = bus)
1867
service = AvahiService(name = server_settings[u"servicename"],
1868
servicetype = u"_mandos._tcp",
1869
protocol = protocol, bus = bus)
2351
1870
if server_settings["interface"]:
2352
1871
service.interface = (if_nametoindex
2353
(str(server_settings["interface"])))
1872
(str(server_settings[u"interface"])))
1875
# Close all input and output, do double fork, etc.
2355
1878
global multiprocessing_manager
2356
1879
multiprocessing_manager = multiprocessing.Manager()
2358
1881
client_class = Client
2360
1883
client_class = functools.partial(ClientDBus, bus = bus)
2362
client_settings = Client.config_parser(client_config)
2363
old_client_settings = {}
2366
# Get client data and settings from last running state.
2367
if server_settings["restore"]:
2369
with open(stored_state_path, "rb") as stored_state:
2370
clients_data, old_client_settings = (pickle.load
2372
os.remove(stored_state_path)
2373
except IOError as e:
2374
if e.errno == errno.ENOENT:
2375
logger.warning("Could not load persistent state: {0}"
2376
.format(os.strerror(e.errno)))
2378
logger.critical("Could not load persistent state:",
2381
except EOFError as e:
2382
logger.warning("Could not load persistent state: "
2383
"EOFError:", exc_info=e)
2385
with PGPEngine() as pgp:
2386
for client_name, client in clients_data.iteritems():
2387
# Decide which value to use after restoring saved state.
2388
# We have three different values: Old config file,
2389
# new config file, and saved state.
2390
# New config value takes precedence if it differs from old
2391
# config value, otherwise use saved state.
2392
for name, value in client_settings[client_name].items():
2394
# For each value in new config, check if it
2395
# differs from the old config value (Except for
2396
# the "secret" attribute)
2397
if (name != "secret" and
2398
value != old_client_settings[client_name]
2400
client[name] = value
2404
# Clients who has passed its expire date can still be
2405
# enabled if its last checker was successful. Clients
2406
# whose checker succeeded before we stored its state is
2407
# assumed to have successfully run all checkers during
2409
if client["enabled"]:
2410
if datetime.datetime.utcnow() >= client["expires"]:
2411
if not client["last_checked_ok"]:
2413
"disabling client {0} - Client never "
2414
"performed a successful checker"
2415
.format(client_name))
2416
client["enabled"] = False
2417
elif client["last_checker_status"] != 0:
2419
"disabling client {0} - Client "
2420
"last checker failed with error code {1}"
2421
.format(client_name,
2422
client["last_checker_status"]))
2423
client["enabled"] = False
2425
client["expires"] = (datetime.datetime
2427
+ client["timeout"])
2428
logger.debug("Last checker succeeded,"
2429
" keeping {0} enabled"
2430
.format(client_name))
1884
def client_config_items(config, section):
1885
special_settings = {
1886
"approved_by_default":
1887
lambda: config.getboolean(section,
1888
"approved_by_default"),
1890
for name, value in config.items(section):
2432
client["secret"] = (
2433
pgp.decrypt(client["encrypted_secret"],
2434
client_settings[client_name]
2437
# If decryption fails, we use secret from new settings
2438
logger.debug("Failed to decrypt {0} old secret"
2439
.format(client_name))
2440
client["secret"] = (
2441
client_settings[client_name]["secret"])
2443
# Add/remove clients based on new changes made to config
2444
for client_name in (set(old_client_settings)
2445
- set(client_settings)):
2446
del clients_data[client_name]
2447
for client_name in (set(client_settings)
2448
- set(old_client_settings)):
2449
clients_data[client_name] = client_settings[client_name]
2451
# Create all client objects
2452
for client_name, client in clients_data.iteritems():
2453
tcp_server.clients[client_name] = client_class(
2454
name = client_name, settings = client)
1892
yield (name, special_settings[name]())
1896
tcp_server.clients.update(set(
1897
client_class(name = section,
1898
config= dict(client_config_items(
1899
client_config, section)))
1900
for section in client_config.sections()))
2456
1901
if not tcp_server.clients:
2457
logger.warning("No clients defined")
1902
logger.warning(u"No clients defined")
2462
1907
pid = os.getpid()
2463
pidfile.write(str(pid) + "\n".encode("utf-8"))
1908
pidfile.write(str(pid) + "\n")
2465
1910
except IOError:
2466
logger.error("Could not write to file %r with PID %d",
1911
logger.error(u"Could not write to file %r with PID %d",
2467
1912
pidfilename, pid)
2468
1913
except NameError:
2469
1914
# "pidfile" was never created
2471
1916
del pidfilename
2472
1918
signal.signal(signal.SIGINT, signal.SIG_IGN)
2474
1920
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2475
1921
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2478
@alternate_dbus_interfaces({"se.recompile.Mandos":
2479
"se.bsnet.fukt.Mandos"})
2480
class MandosDBusService(DBusObjectWithProperties):
1924
class MandosDBusService(dbus.service.Object):
2481
1925
"""A D-Bus proxy object"""
2482
1926
def __init__(self):
2483
dbus.service.Object.__init__(self, bus, "/")
2484
_interface = "se.recompile.Mandos"
2486
@dbus_interface_annotations(_interface)
2488
return { "org.freedesktop.DBus.Property"
2489
".EmitsChangedSignal":
2492
@dbus.service.signal(_interface, signature="o")
1927
dbus.service.Object.__init__(self, bus, u"/")
1928
_interface = u"se.bsnet.fukt.Mandos"
1930
@dbus.service.signal(_interface, signature=u"o")
2493
1931
def ClientAdded(self, objpath):
2497
@dbus.service.signal(_interface, signature="ss")
1935
@dbus.service.signal(_interface, signature=u"ss")
2498
1936
def ClientNotFound(self, fingerprint, address):
2502
@dbus.service.signal(_interface, signature="os")
1940
@dbus.service.signal(_interface, signature=u"os")
2503
1941
def ClientRemoved(self, objpath, name):
2507
@dbus.service.method(_interface, out_signature="ao")
1945
@dbus.service.method(_interface, out_signature=u"ao")
2508
1946
def GetAllClients(self):
2510
1948
return dbus.Array(c.dbus_object_path
2512
tcp_server.clients.itervalues())
1949
for c in tcp_server.clients)
2514
1951
@dbus.service.method(_interface,
2515
out_signature="a{oa{sv}}")
1952
out_signature=u"a{oa{sv}}")
2516
1953
def GetAllClientsWithProperties(self):
2518
1955
return dbus.Dictionary(
2519
((c.dbus_object_path, c.GetAll(""))
2520
for c in tcp_server.clients.itervalues()),
1956
((c.dbus_object_path, c.GetAll(u""))
1957
for c in tcp_server.clients),
1958
signature=u"oa{sv}")
2523
@dbus.service.method(_interface, in_signature="o")
1960
@dbus.service.method(_interface, in_signature=u"o")
2524
1961
def RemoveClient(self, object_path):
2526
for c in tcp_server.clients.itervalues():
1963
for c in tcp_server.clients:
2527
1964
if c.dbus_object_path == object_path:
2528
del tcp_server.clients[c.name]
1965
tcp_server.clients.remove(c)
2529
1966
c.remove_from_connection()
2530
1967
# Don't signal anything except ClientRemoved
2531
1968
c.disable(quiet=True)