80
88
except ImportError:
81
89
SO_BINDTODEVICE = None
86
#logger = logging.getLogger(u'mandos')
87
logger = logging.Logger(u'mandos')
92
stored_state_file = "clients.pickle"
94
logger = logging.getLogger()
88
95
syslogger = (logging.handlers.SysLogHandler
89
96
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
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)
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
102
214
class AvahiError(Exception):
103
215
def __init__(self, value, *args, **kwargs):
197
315
dbus.UInt16(self.port),
198
316
avahi.string_array_to_txt_array(self.TXT))
199
317
self.group.Commit()
200
319
def entry_group_state_changed(self, state, error):
201
320
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
321
logger.debug("Avahi entry group state change: %i", state)
204
323
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
324
logger.debug("Zeroconf service established.")
206
325
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.warning(u"Zeroconf service name collision.")
326
logger.info("Zeroconf service name collision.")
209
328
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
329
logger.critical("Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
331
raise AvahiGroupError("State changed: {0!s}"
214
334
def cleanup(self):
215
335
"""Derived from the Avahi example code"""
216
336
if self.group is not None:
339
except (dbus.exceptions.UnknownMethodException,
340
dbus.exceptions.DBusException):
218
342
self.group = None
219
def server_state_changed(self, state):
345
def server_state_changed(self, state, error=None):
220
346
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
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)
225
362
elif state == avahi.SERVER_RUNNING:
366
logger.debug("Unknown state: %r", state)
368
logger.debug("Unknown state: %r: %r", state, error)
227
370
def activate(self):
228
371
"""Derived from the Avahi example code"""
229
372
if self.server is None:
230
373
self.server = dbus.Interface(
231
374
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
375
avahi.DBUS_PATH_SERVER,
376
follow_name_owner_changes=True),
233
377
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
378
self.server.connect_to_signal("StateChanged",
235
379
self.server_state_changed)
236
380
self.server_state_changed(self.server.GetState())
383
class AvahiServiceToSyslog(AvahiService):
385
"""Add the new name to the syslog messages"""
386
ret = AvahiService.rename(self)
387
syslogger.setFormatter(logging.Formatter
388
('Mandos ({0}) [%(process)d]:'
389
' %(levelname)s: %(message)s'
394
def timedelta_to_milliseconds(td):
395
"Convert a datetime.timedelta() to milliseconds"
396
return ((td.days * 24 * 60 * 60 * 1000)
397
+ (td.seconds * 1000)
398
+ (td.microseconds // 1000))
239
401
class Client(object):
240
402
"""A representation of a client host served by this server.
243
_approved: bool(); 'None' if not yet approved/disapproved
405
approved: bool(); 'None' if not yet approved/disapproved
244
406
approval_delay: datetime.timedelta(); Time to wait for approval
245
407
approval_duration: datetime.timedelta(); Duration of one approval
246
408
checker: subprocess.Popen(); a running checker process used
263
426
interval: datetime.timedelta(); How often to start a new checker
264
427
last_approval_request: datetime.datetime(); (UTC) or None
265
428
last_checked_ok: datetime.datetime(); (UTC) or None
266
last_enabled: datetime.datetime(); (UTC)
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
267
433
name: string; from the config file, used in log messages and
268
434
D-Bus identifiers
269
435
secret: bytestring; sent verbatim (over TLS) to client
270
436
timeout: datetime.timedelta(); How long from last_checked_ok
271
437
until this client is disabled
438
extended_timeout: extra long timeout when secret has been sent
272
439
runtime_expansions: Allowed attributes for runtime expansion.
440
expires: datetime.datetime(); time (UTC) when a client will be
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))
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",
287
459
def timeout_milliseconds(self):
288
460
"Return the 'timeout' attribute in milliseconds"
289
return self._timedelta_to_milliseconds(self.timeout)
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)
291
467
def interval_milliseconds(self):
292
468
"Return the 'interval' attribute in milliseconds"
293
return self._timedelta_to_milliseconds(self.interval)
469
return timedelta_to_milliseconds(self.interval)
295
471
def approval_delay_milliseconds(self):
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'
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):
305
logger.debug(u"Creating client %r", self.name)
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)
306
538
# Uppercase and remove spaces from fingerprint for later
307
539
# comparison purposes with return value from the fingerprint()
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
541
logger.debug(" Fingerprint: %s", self.fingerprint)
542
self.created = settings.get("created",
543
datetime.datetime.utcnow())
545
# attributes specific for this server instance
331
546
self.checker = None
332
547
self.checker_initiator_tag = None
333
548
self.disable_initiator_tag = None
334
549
self.checker_callback_tag = None
335
self.checker_command = config[u"checker"]
336
550
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",
341
552
self.approvals_pending = 0
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())
553
self.changedstate = (multiprocessing_manager
554
.Condition(multiprocessing_manager
556
self.client_structure = [attr for attr in
557
self.__dict__.iterkeys()
558
if not attr.startswith("_")]
559
self.client_structure.append("client_structure")
561
for name, t in inspect.getmembers(type(self),
565
if not name.startswith("_"):
566
self.client_structure.append(name)
568
# Send notice to process children that client state has changed
348
569
def send_changedstate(self):
349
self.changedstate.acquire()
350
self.changedstate.notify_all()
351
self.changedstate.release()
570
with self.changedstate:
571
self.changedstate.notify_all()
353
573
def enable(self):
354
574
"""Start this client's checker and timeout hooks"""
355
if getattr(self, u"enabled", False):
575
if getattr(self, "enabled", False):
356
576
# Already enabled
358
self.send_changedstate()
578
self.expires = datetime.datetime.utcnow() + self.timeout
359
580
self.last_enabled = datetime.datetime.utcnow()
582
self.send_changedstate()
584
def disable(self, quiet=True):
585
"""Disable this client."""
586
if not getattr(self, "enabled", False):
589
logger.info("Disabling client %s", self.name)
590
if getattr(self, "disable_initiator_tag", None) is not None:
591
gobject.source_remove(self.disable_initiator_tag)
592
self.disable_initiator_tag = None
594
if getattr(self, "checker_initiator_tag", None) is not None:
595
gobject.source_remove(self.checker_initiator_tag)
596
self.checker_initiator_tag = None
600
self.send_changedstate()
601
# Do not run this again if called by a gobject.timeout_add
607
def init_checker(self):
360
608
# Schedule a new checker to be started an 'interval' from now,
361
609
# and every interval from then on.
610
if self.checker_initiator_tag is not None:
611
gobject.source_remove(self.checker_initiator_tag)
362
612
self.checker_initiator_tag = (gobject.timeout_add
363
613
(self.interval_milliseconds(),
364
614
self.start_checker))
365
615
# Schedule a disable() when 'timeout' has passed
616
if self.disable_initiator_tag is not None:
617
gobject.source_remove(self.disable_initiator_tag)
366
618
self.disable_initiator_tag = (gobject.timeout_add
367
619
(self.timeout_milliseconds(),
370
621
# Also start a new checker *right now*.
371
622
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
398
624
def checker_callback(self, pid, condition, command):
399
625
"""The checker has completed, so take appropriate actions."""
400
626
self.checker_callback_tag = None
401
627
self.checker = None
402
628
if os.WIFEXITED(condition):
403
exitstatus = os.WEXITSTATUS(condition)
405
logger.info(u"Checker for %(name)s succeeded",
629
self.last_checker_status = os.WEXITSTATUS(condition)
630
if self.last_checker_status == 0:
631
logger.info("Checker for %(name)s succeeded",
407
633
self.checked_ok()
409
logger.info(u"Checker for %(name)s failed",
635
logger.info("Checker for %(name)s failed",
412
logger.warning(u"Checker for %(name)s crashed?",
638
self.last_checker_status = -1
639
logger.warning("Checker for %(name)s crashed?",
415
642
def checked_ok(self):
416
"""Bump up the timeout for this client.
418
This should only be called when the client has been seen,
643
"""Assert that the client has been seen, alive and well."""
421
644
self.last_checked_ok = datetime.datetime.utcnow()
422
gobject.source_remove(self.disable_initiator_tag)
423
self.disable_initiator_tag = (gobject.timeout_add
424
(self.timeout_milliseconds(),
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
427
661
def need_approval(self):
428
662
self.last_approval_request = datetime.datetime.utcnow()
572
836
class DBusObjectWithProperties(dbus.service.Object):
573
837
"""A D-Bus object with properties.
575
839
Classes inheriting from this can use the dbus_service_property
576
840
decorator to expose methods as D-Bus properties. It exposes the
577
841
standard Get(), Set(), and GetAll() methods on the D-Bus.
581
def _is_dbus_property(obj):
582
return getattr(obj, u"_dbus_is_property", False)
845
def _is_dbus_thing(thing):
846
"""Returns a function testing if an attribute is a D-Bus thing
848
If called like _is_dbus_thing("method") it returns a function
849
suitable for use as predicate to inspect.getmembers().
851
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
584
def _get_all_dbus_properties(self):
854
def _get_all_dbus_things(self, thing):
585
855
"""Returns a generator of (name, attribute) pairs
587
return ((prop._dbus_name, prop)
589
inspect.getmembers(self, self._is_dbus_property))
857
return ((getattr(athing.__get__(self), "_dbus_name",
859
athing.__get__(self))
860
for cls in self.__class__.__mro__
862
inspect.getmembers(cls,
863
self._is_dbus_thing(thing)))
591
865
def _get_dbus_property(self, interface_name, property_name):
592
866
"""Returns a bound method if one exists which is a D-Bus
593
867
property with the specified name and interface.
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)):
869
for cls in self.__class__.__mro__:
870
for name, value in (inspect.getmembers
872
self._is_dbus_thing("property"))):
873
if (value._dbus_name == property_name
874
and value._dbus_interface == interface_name):
875
return value.__get__(self)
605
877
# No such property
606
raise DBusPropertyNotFound(self.dbus_object_path + u":"
607
+ interface_name + u"."
878
raise DBusPropertyNotFound(self.dbus_object_path + ":"
879
+ interface_name + "."
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
882
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
612
884
def Get(self, interface_name, property_name):
613
885
"""Standard D-Bus property Get() method, see D-Bus standard.
615
887
prop = self._get_dbus_property(interface_name, property_name)
616
if prop._dbus_access == u"write":
888
if prop._dbus_access == "write":
617
889
raise DBusPropertyAccessException(property_name)
619
if not hasattr(value, u"variant_level"):
891
if not hasattr(value, "variant_level"):
621
893
return type(value)(value, variant_level=value.variant_level+1)
623
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
895
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
624
896
def Set(self, interface_name, property_name, value):
625
897
"""Standard D-Bus property Set() method, see D-Bus standard.
627
899
prop = self._get_dbus_property(interface_name, property_name)
628
if prop._dbus_access == u"read":
900
if prop._dbus_access == "read":
629
901
raise DBusPropertyAccessException(property_name)
630
if prop._dbus_get_args_options[u"byte_arrays"]:
902
if prop._dbus_get_args_options["byte_arrays"]:
631
903
# The byte_arrays option is not supported yet on
632
904
# signatures other than "ay".
633
if prop._dbus_signature != u"ay":
905
if prop._dbus_signature != "ay":
635
value = dbus.ByteArray(''.join(unichr(byte)
907
value = dbus.ByteArray(b''.join(chr(byte)
639
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
640
out_signature=u"a{sv}")
911
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
912
out_signature="a{sv}")
641
913
def GetAll(self, interface_name):
642
914
"""Standard D-Bus property GetAll() method, see D-Bus
645
917
Note: Will not include properties with access="write".
648
for name, prop in self._get_all_dbus_properties():
920
for name, prop in self._get_all_dbus_things("property"):
649
921
if (interface_name
650
922
and interface_name != prop._dbus_interface):
651
923
# Interface non-empty but did not match
653
925
# Ignore write-only properties
654
if prop._dbus_access == u"write":
926
if prop._dbus_access == "write":
657
if not hasattr(value, u"variant_level"):
929
if not hasattr(value, "variant_level"):
930
properties[name] = value
660
all[name] = type(value)(value, variant_level=
661
value.variant_level+1)
662
return dbus.Dictionary(all, signature=u"sv")
932
properties[name] = type(value)(value, variant_level=
933
value.variant_level+1)
934
return dbus.Dictionary(properties, signature="sv")
664
936
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
666
938
path_keyword='object_path',
667
939
connection_keyword='connection')
668
940
def Introspect(self, object_path, connection):
669
"""Standard D-Bus method, overloaded to insert property tags.
941
"""Overloading of standard D-Bus method.
943
Inserts property tags and interface annotation tags.
671
945
xmlstring = dbus.service.Object.Introspect(self, object_path,
674
948
document = xml.dom.minidom.parseString(xmlstring)
675
949
def make_tag(document, name, prop):
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)
950
e = document.createElement("property")
951
e.setAttribute("name", name)
952
e.setAttribute("type", prop._dbus_signature)
953
e.setAttribute("access", prop._dbus_access)
681
for if_tag in document.getElementsByTagName(u"interface"):
955
for if_tag in document.getElementsByTagName("interface"):
682
957
for tag in (make_tag(document, name, prop)
684
in self._get_all_dbus_properties()
959
in self._get_all_dbus_things("property")
685
960
if prop._dbus_interface
686
== if_tag.getAttribute(u"name")):
961
== if_tag.getAttribute("name")):
687
962
if_tag.appendChild(tag)
963
# Add annotation tags
964
for typ in ("method", "signal", "property"):
965
for tag in if_tag.getElementsByTagName(typ):
967
for name, prop in (self.
968
_get_all_dbus_things(typ)):
969
if (name == tag.getAttribute("name")
970
and prop._dbus_interface
971
== if_tag.getAttribute("name")):
972
annots.update(getattr
976
for name, value in annots.iteritems():
977
ann_tag = document.createElement(
979
ann_tag.setAttribute("name", name)
980
ann_tag.setAttribute("value", value)
981
tag.appendChild(ann_tag)
982
# Add interface annotation tags
983
for annotation, value in dict(
984
itertools.chain.from_iterable(
985
annotations().iteritems()
986
for name, annotations in
987
self._get_all_dbus_things("interface")
988
if name == if_tag.getAttribute("name")
990
ann_tag = document.createElement("annotation")
991
ann_tag.setAttribute("name", annotation)
992
ann_tag.setAttribute("value", value)
993
if_tag.appendChild(ann_tag)
688
994
# Add the names to the return values for the
689
995
# "org.freedesktop.DBus.Properties" methods
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")
996
if (if_tag.getAttribute("name")
997
== "org.freedesktop.DBus.Properties"):
998
for cn in if_tag.getElementsByTagName("method"):
999
if cn.getAttribute("name") == "Get":
1000
for arg in cn.getElementsByTagName("arg"):
1001
if (arg.getAttribute("direction")
1003
arg.setAttribute("name", "value")
1004
elif cn.getAttribute("name") == "GetAll":
1005
for arg in cn.getElementsByTagName("arg"):
1006
if (arg.getAttribute("direction")
1008
arg.setAttribute("name", "props")
1009
xmlstring = document.toxml("utf-8")
704
1010
document.unlink()
705
1011
except (AttributeError, xml.dom.DOMException,
706
xml.parsers.expat.ExpatError), error:
707
logger.error(u"Failed to override Introspection method",
1012
xml.parsers.expat.ExpatError) as error:
1013
logger.error("Failed to override Introspection method",
709
1015
return xmlstring
1018
def datetime_to_dbus (dt, variant_level=0):
1019
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1021
return dbus.String("", variant_level = variant_level)
1022
return dbus.String(dt.isoformat(),
1023
variant_level=variant_level)
1026
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1027
"""A class decorator; applied to a subclass of
1028
dbus.service.Object, it will add alternate D-Bus attributes with
1029
interface names according to the "alt_interface_names" mapping.
1032
@alternate_dbus_names({"org.example.Interface":
1033
"net.example.AlternateInterface"})
1034
class SampleDBusObject(dbus.service.Object):
1035
@dbus.service.method("org.example.Interface")
1036
def SampleDBusMethod():
1039
The above "SampleDBusMethod" on "SampleDBusObject" will be
1040
reachable via two interfaces: "org.example.Interface" and
1041
"net.example.AlternateInterface", the latter of which will have
1042
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1043
"true", unless "deprecate" is passed with a False value.
1045
This works for methods and signals, and also for D-Bus properties
1046
(from DBusObjectWithProperties) and interfaces (from the
1047
dbus_interface_annotations decorator).
1050
for orig_interface_name, alt_interface_name in (
1051
alt_interface_names.iteritems()):
1053
interface_names = set()
1054
# Go though all attributes of the class
1055
for attrname, attribute in inspect.getmembers(cls):
1056
# Ignore non-D-Bus attributes, and D-Bus attributes
1057
# with the wrong interface name
1058
if (not hasattr(attribute, "_dbus_interface")
1059
or not attribute._dbus_interface
1060
.startswith(orig_interface_name)):
1062
# Create an alternate D-Bus interface name based on
1064
alt_interface = (attribute._dbus_interface
1065
.replace(orig_interface_name,
1066
alt_interface_name))
1067
interface_names.add(alt_interface)
1068
# Is this a D-Bus signal?
1069
if getattr(attribute, "_dbus_is_signal", False):
1070
# Extract the original non-method function by
1072
nonmethod_func = (dict(
1073
zip(attribute.func_code.co_freevars,
1074
attribute.__closure__))["func"]
1076
# Create a new, but exactly alike, function
1077
# object, and decorate it to be a new D-Bus signal
1078
# with the alternate D-Bus interface name
1079
new_function = (dbus.service.signal
1081
attribute._dbus_signature)
1082
(types.FunctionType(
1083
nonmethod_func.func_code,
1084
nonmethod_func.func_globals,
1085
nonmethod_func.func_name,
1086
nonmethod_func.func_defaults,
1087
nonmethod_func.func_closure)))
1088
# Copy annotations, if any
1090
new_function._dbus_annotations = (
1091
dict(attribute._dbus_annotations))
1092
except AttributeError:
1094
# Define a creator of a function to call both the
1095
# original and alternate functions, so both the
1096
# original and alternate signals gets sent when
1097
# the function is called
1098
def fixscope(func1, func2):
1099
"""This function is a scope container to pass
1100
func1 and func2 to the "call_both" function
1101
outside of its arguments"""
1102
def call_both(*args, **kwargs):
1103
"""This function will emit two D-Bus
1104
signals by calling func1 and func2"""
1105
func1(*args, **kwargs)
1106
func2(*args, **kwargs)
1108
# Create the "call_both" function and add it to
1110
attr[attrname] = fixscope(attribute, new_function)
1111
# Is this a D-Bus method?
1112
elif getattr(attribute, "_dbus_is_method", False):
1113
# Create a new, but exactly alike, function
1114
# object. Decorate it to be a new D-Bus method
1115
# with the alternate D-Bus interface name. Add it
1117
attr[attrname] = (dbus.service.method
1119
attribute._dbus_in_signature,
1120
attribute._dbus_out_signature)
1122
(attribute.func_code,
1123
attribute.func_globals,
1124
attribute.func_name,
1125
attribute.func_defaults,
1126
attribute.func_closure)))
1127
# Copy annotations, if any
1129
attr[attrname]._dbus_annotations = (
1130
dict(attribute._dbus_annotations))
1131
except AttributeError:
1133
# Is this a D-Bus property?
1134
elif getattr(attribute, "_dbus_is_property", False):
1135
# Create a new, but exactly alike, function
1136
# object, and decorate it to be a new D-Bus
1137
# property with the alternate D-Bus interface
1138
# name. Add it to the class.
1139
attr[attrname] = (dbus_service_property
1141
attribute._dbus_signature,
1142
attribute._dbus_access,
1144
._dbus_get_args_options
1147
(attribute.func_code,
1148
attribute.func_globals,
1149
attribute.func_name,
1150
attribute.func_defaults,
1151
attribute.func_closure)))
1152
# Copy annotations, if any
1154
attr[attrname]._dbus_annotations = (
1155
dict(attribute._dbus_annotations))
1156
except AttributeError:
1158
# Is this a D-Bus interface?
1159
elif getattr(attribute, "_dbus_is_interface", False):
1160
# Create a new, but exactly alike, function
1161
# object. Decorate it to be a new D-Bus interface
1162
# with the alternate D-Bus interface name. Add it
1164
attr[attrname] = (dbus_interface_annotations
1167
(attribute.func_code,
1168
attribute.func_globals,
1169
attribute.func_name,
1170
attribute.func_defaults,
1171
attribute.func_closure)))
1173
# Deprecate all alternate interfaces
1174
iname="_AlternateDBusNames_interface_annotation{0}"
1175
for interface_name in interface_names:
1176
@dbus_interface_annotations(interface_name)
1178
return { "org.freedesktop.DBus.Deprecated":
1180
# Find an unused name
1181
for aname in (iname.format(i)
1182
for i in itertools.count()):
1183
if aname not in attr:
1187
# Replace the class with a new subclass of it with
1188
# methods, signals, etc. as created above.
1189
cls = type(b"{0}Alternate".format(cls.__name__),
1195
@alternate_dbus_interfaces({"se.recompile.Mandos":
1196
"se.bsnet.fukt.Mandos"})
712
1197
class ClientDBus(Client, DBusObjectWithProperties):
713
1198
"""A Client class using D-Bus
720
1205
runtime_expansions = (Client.runtime_expansions
721
+ (u"dbus_object_path",))
1206
+ ("dbus_object_path",))
723
1208
# dbus.service.Object doesn't use super(), so we can't either.
725
1210
def __init__(self, bus = None, *args, **kwargs):
726
self._approvals_pending = 0
728
1212
Client.__init__(self, *args, **kwargs)
729
1213
# Only now, when this client is initialized, can it show up on
731
1215
client_object_name = unicode(self.name).translate(
732
{ord(u"."): ord(u"_"),
733
ord(u"-"): ord(u"_")})
1216
{ord("."): ord("_"),
1217
ord("-"): ord("_")})
734
1218
self.dbus_object_path = (dbus.ObjectPath
735
(u"/clients/" + client_object_name))
1219
("/clients/" + client_object_name))
736
1220
DBusObjectWithProperties.__init__(self, self.bus,
737
1221
self.dbus_object_path)
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))
1223
def notifychangeproperty(transform_func,
1224
dbus_name, type_func=lambda x: x,
1226
""" Modify a variable so that it's a property which announces
1227
its changes to DBus.
1229
transform_fun: Function that takes a value and a variant_level
1230
and transforms it to a D-Bus type.
1231
dbus_name: D-Bus name of the variable
1232
type_func: Function that transform the value before sending it
1233
to the D-Bus. Default: no transform
1234
variant_level: D-Bus variant level. Default: 1
1236
attrname = "_{0}".format(dbus_name)
1237
def setter(self, value):
1238
if hasattr(self, "dbus_object_path"):
1239
if (not hasattr(self, attrname) or
1240
type_func(getattr(self, attrname, None))
1241
!= type_func(value)):
1242
dbus_value = transform_func(type_func(value),
1245
self.PropertyChanged(dbus.String(dbus_name),
1247
setattr(self, attrname, value)
1249
return property(lambda self: getattr(self, attrname), setter)
1251
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1252
approvals_pending = notifychangeproperty(dbus.Boolean,
1255
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1256
last_enabled = notifychangeproperty(datetime_to_dbus,
1258
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1259
type_func = lambda checker:
1260
checker is not None)
1261
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1263
last_checker_status = notifychangeproperty(dbus.Int16,
1264
"LastCheckerStatus")
1265
last_approval_request = notifychangeproperty(
1266
datetime_to_dbus, "LastApprovalRequest")
1267
approved_by_default = notifychangeproperty(dbus.Boolean,
1268
"ApprovedByDefault")
1269
approval_delay = notifychangeproperty(dbus.UInt64,
1272
timedelta_to_milliseconds)
1273
approval_duration = notifychangeproperty(
1274
dbus.UInt64, "ApprovalDuration",
1275
type_func = timedelta_to_milliseconds)
1276
host = notifychangeproperty(dbus.String, "Host")
1277
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1279
timedelta_to_milliseconds)
1280
extended_timeout = notifychangeproperty(
1281
dbus.UInt64, "ExtendedTimeout",
1282
type_func = timedelta_to_milliseconds)
1283
interval = notifychangeproperty(dbus.UInt64,
1286
timedelta_to_milliseconds)
1287
checker_command = notifychangeproperty(dbus.String, "Checker")
1289
del notifychangeproperty
783
1291
def __del__(self, *args, **kwargs):
785
1293
self.remove_from_connection()
786
1294
except LookupError:
788
if hasattr(DBusObjectWithProperties, u"__del__"):
1296
if hasattr(DBusObjectWithProperties, "__del__"):
789
1297
DBusObjectWithProperties.__del__(self, *args, **kwargs)
790
1298
Client.__del__(self, *args, **kwargs)
951
1432
# ApprovalPending - property
952
@dbus_service_property(_interface, signature=u"b", access=u"read")
1433
@dbus_service_property(_interface, signature="b", access="read")
953
1434
def ApprovalPending_dbus_property(self):
954
1435
return dbus.Boolean(bool(self.approvals_pending))
956
1437
# ApprovedByDefault - property
957
@dbus_service_property(_interface, signature=u"b",
1438
@dbus_service_property(_interface, signature="b",
959
1440
def ApprovedByDefault_dbus_property(self, value=None):
960
1441
if value is None: # get
961
1442
return dbus.Boolean(self.approved_by_default)
962
1443
self.approved_by_default = bool(value)
964
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
965
dbus.Boolean(value, variant_level=1))
967
1445
# ApprovalDelay - property
968
@dbus_service_property(_interface, signature=u"t",
1446
@dbus_service_property(_interface, signature="t",
970
1448
def ApprovalDelay_dbus_property(self, value=None):
971
1449
if value is None: # get
972
1450
return dbus.UInt64(self.approval_delay_milliseconds())
973
1451
self.approval_delay = datetime.timedelta(0, 0, 0, value)
975
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
976
dbus.UInt64(value, variant_level=1))
978
1453
# ApprovalDuration - property
979
@dbus_service_property(_interface, signature=u"t",
1454
@dbus_service_property(_interface, signature="t",
981
1456
def ApprovalDuration_dbus_property(self, value=None):
982
1457
if value is None: # get
983
return dbus.UInt64(self._timedelta_to_milliseconds(
1458
return dbus.UInt64(timedelta_to_milliseconds(
984
1459
self.approval_duration))
985
1460
self.approval_duration = datetime.timedelta(0, 0, 0, value)
987
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
988
dbus.UInt64(value, variant_level=1))
990
1462
# Name - property
991
@dbus_service_property(_interface, signature=u"s", access=u"read")
1463
@dbus_service_property(_interface, signature="s", access="read")
992
1464
def Name_dbus_property(self):
993
1465
return dbus.String(self.name)
995
1467
# Fingerprint - property
996
@dbus_service_property(_interface, signature=u"s", access=u"read")
1468
@dbus_service_property(_interface, signature="s", access="read")
997
1469
def Fingerprint_dbus_property(self):
998
1470
return dbus.String(self.fingerprint)
1000
1472
# Host - property
1001
@dbus_service_property(_interface, signature=u"s",
1002
access=u"readwrite")
1473
@dbus_service_property(_interface, signature="s",
1003
1475
def Host_dbus_property(self, value=None):
1004
1476
if value is None: # get
1005
1477
return dbus.String(self.host)
1008
self.PropertyChanged(dbus.String(u"Host"),
1009
dbus.String(value, variant_level=1))
1478
self.host = unicode(value)
1011
1480
# Created - property
1012
@dbus_service_property(_interface, signature=u"s", access=u"read")
1481
@dbus_service_property(_interface, signature="s", access="read")
1013
1482
def Created_dbus_property(self):
1014
return dbus.String(self._datetime_to_dbus(self.created))
1483
return datetime_to_dbus(self.created)
1016
1485
# LastEnabled - property
1017
@dbus_service_property(_interface, signature=u"s", access=u"read")
1486
@dbus_service_property(_interface, signature="s", access="read")
1018
1487
def LastEnabled_dbus_property(self):
1019
if self.last_enabled is None:
1020
return dbus.String(u"")
1021
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1488
return datetime_to_dbus(self.last_enabled)
1023
1490
# Enabled - property
1024
@dbus_service_property(_interface, signature=u"b",
1025
access=u"readwrite")
1491
@dbus_service_property(_interface, signature="b",
1026
1493
def Enabled_dbus_property(self, value=None):
1027
1494
if value is None: # get
1028
1495
return dbus.Boolean(self.enabled)
1034
1501
# LastCheckedOK - property
1035
@dbus_service_property(_interface, signature=u"s",
1036
access=u"readwrite")
1502
@dbus_service_property(_interface, signature="s",
1037
1504
def LastCheckedOK_dbus_property(self, value=None):
1038
1505
if value is not None:
1039
1506
self.checked_ok()
1041
if self.last_checked_ok is None:
1042
return dbus.String(u"")
1043
return dbus.String(self._datetime_to_dbus(self
1508
return datetime_to_dbus(self.last_checked_ok)
1510
# LastCheckerStatus - property
1511
@dbus_service_property(_interface, signature="n",
1513
def LastCheckerStatus_dbus_property(self):
1514
return dbus.Int16(self.last_checker_status)
1516
# Expires - property
1517
@dbus_service_property(_interface, signature="s", access="read")
1518
def Expires_dbus_property(self):
1519
return datetime_to_dbus(self.expires)
1046
1521
# LastApprovalRequest - property
1047
@dbus_service_property(_interface, signature=u"s", access=u"read")
1522
@dbus_service_property(_interface, signature="s", access="read")
1048
1523
def LastApprovalRequest_dbus_property(self):
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))
1524
return datetime_to_dbus(self.last_approval_request)
1055
1526
# Timeout - property
1056
@dbus_service_property(_interface, signature=u"t",
1057
access=u"readwrite")
1527
@dbus_service_property(_interface, signature="t",
1058
1529
def Timeout_dbus_property(self, value=None):
1059
1530
if value is None: # get
1060
1531
return dbus.UInt64(self.timeout_milliseconds())
1532
old_timeout = self.timeout
1061
1533
self.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))
1534
# Reschedule disabling
1536
now = datetime.datetime.utcnow()
1537
self.expires += self.timeout - old_timeout
1538
if self.expires <= now:
1539
# The timeout has passed
1542
if (getattr(self, "disable_initiator_tag", None)
1545
gobject.source_remove(self.disable_initiator_tag)
1546
self.disable_initiator_tag = (
1547
gobject.timeout_add(
1548
timedelta_to_milliseconds(self.expires - now),
1551
# ExtendedTimeout - property
1552
@dbus_service_property(_interface, signature="t",
1554
def ExtendedTimeout_dbus_property(self, value=None):
1555
if value is None: # get
1556
return dbus.UInt64(self.extended_timeout_milliseconds())
1557
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1083
1559
# Interval - property
1084
@dbus_service_property(_interface, signature=u"t",
1085
access=u"readwrite")
1560
@dbus_service_property(_interface, signature="t",
1086
1562
def Interval_dbus_property(self, value=None):
1087
1563
if value is None: # get
1088
1564
return dbus.UInt64(self.interval_milliseconds())
1089
1565
self.interval = datetime.timedelta(0, 0, 0, value)
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:
1566
if getattr(self, "checker_initiator_tag", None) is None:
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
1569
# Reschedule checker run
1570
gobject.source_remove(self.checker_initiator_tag)
1571
self.checker_initiator_tag = (gobject.timeout_add
1572
(value, self.start_checker))
1573
self.start_checker() # Start one now, too
1101
1575
# Checker - property
1102
@dbus_service_property(_interface, signature=u"s",
1103
access=u"readwrite")
1576
@dbus_service_property(_interface, signature="s",
1104
1578
def Checker_dbus_property(self, value=None):
1105
1579
if value is None: # get
1106
1580
return dbus.String(self.checker_command)
1107
self.checker_command = value
1109
self.PropertyChanged(dbus.String(u"Checker"),
1110
dbus.String(self.checker_command,
1581
self.checker_command = unicode(value)
1113
1583
# CheckerRunning - property
1114
@dbus_service_property(_interface, signature=u"b",
1115
access=u"readwrite")
1584
@dbus_service_property(_interface, signature="b",
1116
1586
def CheckerRunning_dbus_property(self, value=None):
1117
1587
if value is None: # get
1118
1588
return dbus.Boolean(self.checker is not None)
1170
1640
def handle(self):
1171
1641
with contextlib.closing(self.server.child_pipe) as child_pipe:
1172
logger.info(u"TCP connection from: %s",
1642
logger.info("TCP connection from: %s",
1173
1643
unicode(self.client_address))
1174
logger.debug(u"Pipe FD: %d",
1644
logger.debug("Pipe FD: %d",
1175
1645
self.server.child_pipe.fileno())
1177
1647
session = (gnutls.connection
1178
1648
.ClientSession(self.request,
1179
1649
gnutls.connection
1180
1650
.X509Credentials()))
1182
1652
# Note: gnutls.connection.X509Credentials is really a
1183
1653
# generic GnuTLS certificate credentials object so long as
1184
1654
# no X.509 keys are added to it. Therefore, we can use it
1185
1655
# here despite using OpenPGP certificates.
1187
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1188
# u"+AES-256-CBC", u"+SHA1",
1189
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1657
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1658
# "+AES-256-CBC", "+SHA1",
1659
# "+COMP-NULL", "+CTYPE-OPENPGP",
1191
1661
# Use a fallback default, since this MUST be set.
1192
1662
priority = self.server.gnutls_priority
1193
1663
if priority is None:
1194
priority = u"NORMAL"
1195
1665
(gnutls.library.functions
1196
1666
.gnutls_priority_set_direct(session._c_object,
1197
1667
priority, None))
1199
1669
# Start communication using the Mandos protocol
1200
1670
# Get protocol number
1201
1671
line = self.request.makefile().readline()
1202
logger.debug(u"Protocol version: %r", line)
1672
logger.debug("Protocol version: %r", line)
1204
1674
if int(line.strip().split()[0]) > 1:
1205
1675
raise RuntimeError
1206
except (ValueError, IndexError, RuntimeError), error:
1207
logger.error(u"Unknown protocol version: %s", error)
1676
except (ValueError, IndexError, RuntimeError) as error:
1677
logger.error("Unknown protocol version: %s", error)
1210
1680
# Start GnuTLS connection
1212
1682
session.handshake()
1213
except gnutls.errors.GNUTLSError, error:
1214
logger.warning(u"Handshake failed: %s", error)
1683
except gnutls.errors.GNUTLSError as error:
1684
logger.warning("Handshake failed: %s", error)
1215
1685
# Do not run session.bye() here: the session is not
1216
1686
# established. Just abandon the request.
1218
logger.debug(u"Handshake succeeded")
1688
logger.debug("Handshake succeeded")
1220
1690
approval_required = False
1223
1693
fpr = self.fingerprint(self.peer_certificate
1225
except (TypeError, gnutls.errors.GNUTLSError), error:
1226
logger.warning(u"Bad certificate: %s", error)
1696
gnutls.errors.GNUTLSError) as error:
1697
logger.warning("Bad certificate: %s", error)
1228
logger.debug(u"Fingerprint: %s", fpr)
1699
logger.debug("Fingerprint: %s", fpr)
1231
1702
client = ProxyClient(child_pipe, fpr,
1232
1703
self.client_address)
1672
2131
##################################################################
1673
2132
# Parsing of options, both command line and config file
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]
2134
parser = argparse.ArgumentParser()
2135
parser.add_argument("-v", "--version", action="version",
2136
version = "%(prog)s {0}".format(version),
2137
help="show version number and exit")
2138
parser.add_argument("-i", "--interface", metavar="IF",
2139
help="Bind to interface IF")
2140
parser.add_argument("-a", "--address",
2141
help="Address to listen for requests on")
2142
parser.add_argument("-p", "--port", type=int,
2143
help="Port number to receive requests on")
2144
parser.add_argument("--check", action="store_true",
2145
help="Run self-test")
2146
parser.add_argument("--debug", action="store_true",
2147
help="Debug mode; run in foreground and log"
2149
parser.add_argument("--debuglevel", metavar="LEVEL",
2150
help="Debug level for stdout output")
2151
parser.add_argument("--priority", help="GnuTLS"
2152
" priority string (see GnuTLS documentation)")
2153
parser.add_argument("--servicename",
2154
metavar="NAME", help="Zeroconf service name")
2155
parser.add_argument("--configdir",
2156
default="/etc/mandos", metavar="DIR",
2157
help="Directory to search for configuration"
2159
parser.add_argument("--no-dbus", action="store_false",
2160
dest="use_dbus", help="Do not provide D-Bus"
2161
" system bus interface")
2162
parser.add_argument("--no-ipv6", action="store_false",
2163
dest="use_ipv6", help="Do not use IPv6")
2164
parser.add_argument("--no-restore", action="store_false",
2165
dest="restore", help="Do not restore stored"
2167
parser.add_argument("--statedir", metavar="DIR",
2168
help="Directory to save/restore state in")
2170
options = parser.parse_args()
1704
2172
if options.check:
1753
2224
##################################################################
1755
2226
# For convenience
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":
2227
debug = server_settings["debug"]
2228
debuglevel = server_settings["debuglevel"]
2229
use_dbus = server_settings["use_dbus"]
2230
use_ipv6 = server_settings["use_ipv6"]
2231
stored_state_path = os.path.join(server_settings["statedir"],
2235
initlogger(debug, logging.DEBUG)
2240
level = getattr(logging, debuglevel.upper())
2241
initlogger(debug, level)
2243
if server_settings["servicename"] != "Mandos":
1762
2244
syslogger.setFormatter(logging.Formatter
1763
(u'Mandos (%s) [%%(process)d]:'
1764
u' %%(levelname)s: %%(message)s'
1765
% server_settings[u"servicename"]))
2245
('Mandos ({0}) [%(process)d]:'
2246
' %(levelname)s: %(message)s'
2247
.format(server_settings
1767
2250
# Parse config file with clients
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"],
2251
client_config = configparser.SafeConfigParser(Client
2253
client_config.read(os.path.join(server_settings["configdir"],
1779
2256
global mandos_dbus_service
1780
2257
mandos_dbus_service = None
1782
tcp_server = MandosServer((server_settings[u"address"],
1783
server_settings[u"port"]),
2259
tcp_server = MandosServer((server_settings["address"],
2260
server_settings["port"]),
1785
interface=(server_settings[u"interface"]
2262
interface=(server_settings["interface"]
1787
2264
use_ipv6=use_ipv6,
1788
2265
gnutls_priority=
1789
server_settings[u"priority"],
2266
server_settings["priority"],
1790
2267
use_dbus=use_dbus)
1792
pidfilename = u"/var/run/mandos.pid"
2269
pidfilename = "/var/run/mandos.pid"
1794
pidfile = open(pidfilename, u"w")
1796
logger.error(u"Could not open file %r", pidfilename)
2271
pidfile = open(pidfilename, "w")
2272
except IOError as e:
2273
logger.error("Could not open file %r", pidfilename,
1799
uid = pwd.getpwnam(u"_mandos").pw_uid
1800
gid = pwd.getpwnam(u"_mandos").pw_gid
2276
for name in ("_mandos", "mandos", "nobody"):
1803
uid = pwd.getpwnam(u"mandos").pw_uid
1804
gid = pwd.getpwnam(u"mandos").pw_gid
2278
uid = pwd.getpwnam(name).pw_uid
2279
gid = pwd.getpwnam(name).pw_gid
1805
2281
except KeyError:
1807
uid = pwd.getpwnam(u"nobody").pw_uid
1808
gid = pwd.getpwnam(u"nobody").pw_gid
1815
except OSError, error:
1816
if error[0] != errno.EPERM:
2289
except OSError as error:
2290
if error.errno != 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)
1828
2294
# Enable all possible GnuTLS debugging
1834
2300
@gnutls.library.types.gnutls_log_func
1835
2301
def debug_gnutls(level, string):
1836
logger.debug(u"GnuTLS: %s", string[:-1])
2302
logger.debug("GnuTLS: %s", string[:-1])
1838
2304
(gnutls.library.functions
1839
2305
.gnutls_global_set_log_function(debug_gnutls))
1841
2307
# Redirect stdin so all checkers get /dev/null
1842
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2308
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1843
2309
os.dup2(null, sys.stdin.fileno())
1847
# No console logging
1848
logger.removeHandler(console)
2313
# Need to fork before connecting to D-Bus
2315
# Close all input and output, do double fork, etc.
2318
gobject.threads_init()
1851
2320
global main_loop
1852
2321
# From the Avahi example code
1853
DBusGMainLoop(set_as_default=True )
2322
DBusGMainLoop(set_as_default=True)
1854
2323
main_loop = gobject.MainLoop()
1855
2324
bus = dbus.SystemBus()
1856
2325
# End of Avahi example code
1859
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2328
bus_name = dbus.service.BusName("se.recompile.Mandos",
1860
2329
bus, do_not_queue=True)
1861
except dbus.exceptions.NameExistsException, e:
1862
logger.error(unicode(e) + u", disabling D-Bus")
2330
old_bus_name = (dbus.service.BusName
2331
("se.bsnet.fukt.Mandos", bus,
2333
except dbus.exceptions.NameExistsException as e:
2334
logger.error("Disabling D-Bus:", exc_info=e)
1863
2335
use_dbus = False
1864
server_settings[u"use_dbus"] = False
2336
server_settings["use_dbus"] = False
1865
2337
tcp_server.use_dbus = False
1866
2338
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1867
service = AvahiService(name = server_settings[u"servicename"],
1868
servicetype = u"_mandos._tcp",
1869
protocol = protocol, bus = bus)
2339
service = AvahiServiceToSyslog(name =
2340
server_settings["servicename"],
2341
servicetype = "_mandos._tcp",
2342
protocol = protocol, bus = bus)
1870
2343
if server_settings["interface"]:
1871
2344
service.interface = (if_nametoindex
1872
(str(server_settings[u"interface"])))
1875
# Close all input and output, do double fork, etc.
2345
(str(server_settings["interface"])))
1878
2347
global multiprocessing_manager
1879
2348
multiprocessing_manager = multiprocessing.Manager()
1881
2350
client_class = Client
1883
2352
client_class = functools.partial(ClientDBus, bus = bus)
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):
2354
client_settings = Client.config_parser(client_config)
2355
old_client_settings = {}
2358
# Get client data and settings from last running state.
2359
if server_settings["restore"]:
2361
with open(stored_state_path, "rb") as stored_state:
2362
clients_data, old_client_settings = (pickle.load
2364
os.remove(stored_state_path)
2365
except IOError as e:
2366
if e.errno == errno.ENOENT:
2367
logger.warning("Could not load persistent state: {0}"
2368
.format(os.strerror(e.errno)))
2370
logger.critical("Could not load persistent state:",
2373
except EOFError as e:
2374
logger.warning("Could not load persistent state: "
2375
"EOFError:", exc_info=e)
2377
with PGPEngine() as pgp:
2378
for client_name, client in clients_data.iteritems():
2379
# Decide which value to use after restoring saved state.
2380
# We have three different values: Old config file,
2381
# new config file, and saved state.
2382
# New config value takes precedence if it differs from old
2383
# config value, otherwise use saved state.
2384
for name, value in client_settings[client_name].items():
2386
# For each value in new config, check if it
2387
# differs from the old config value (Except for
2388
# the "secret" attribute)
2389
if (name != "secret" and
2390
value != old_client_settings[client_name]
2392
client[name] = value
2396
# Clients who has passed its expire date can still be
2397
# enabled if its last checker was successful. Clients
2398
# whose checker succeeded before we stored its state is
2399
# assumed to have successfully run all checkers during
2401
if client["enabled"]:
2402
if datetime.datetime.utcnow() >= client["expires"]:
2403
if not client["last_checked_ok"]:
2405
"disabling client {0} - Client never "
2406
"performed a successful checker"
2407
.format(client_name))
2408
client["enabled"] = False
2409
elif client["last_checker_status"] != 0:
2411
"disabling client {0} - Client "
2412
"last checker failed with error code {1}"
2413
.format(client_name,
2414
client["last_checker_status"]))
2415
client["enabled"] = False
2417
client["expires"] = (datetime.datetime
2419
+ client["timeout"])
2420
logger.debug("Last checker succeeded,"
2421
" keeping {0} enabled"
2422
.format(client_name))
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()))
2424
client["secret"] = (
2425
pgp.decrypt(client["encrypted_secret"],
2426
client_settings[client_name]
2429
# If decryption fails, we use secret from new settings
2430
logger.debug("Failed to decrypt {0} old secret"
2431
.format(client_name))
2432
client["secret"] = (
2433
client_settings[client_name]["secret"])
2435
# Add/remove clients based on new changes made to config
2436
for client_name in (set(old_client_settings)
2437
- set(client_settings)):
2438
del clients_data[client_name]
2439
for client_name in (set(client_settings)
2440
- set(old_client_settings)):
2441
clients_data[client_name] = client_settings[client_name]
2443
# Create all client objects
2444
for client_name, client in clients_data.iteritems():
2445
tcp_server.clients[client_name] = client_class(
2446
name = client_name, settings = client)
1901
2448
if not tcp_server.clients:
1902
logger.warning(u"No clients defined")
2449
logger.warning("No clients defined")
1907
2454
pid = os.getpid()
1908
pidfile.write(str(pid) + "\n")
2455
pidfile.write(str(pid) + "\n".encode("utf-8"))
1910
2457
except IOError:
1911
logger.error(u"Could not write to file %r with PID %d",
2458
logger.error("Could not write to file %r with PID %d",
1912
2459
pidfilename, pid)
1913
2460
except NameError:
1914
2461
# "pidfile" was never created
1916
2463
del pidfilename
1918
2464
signal.signal(signal.SIGINT, signal.SIG_IGN)
1920
2466
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1921
2467
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1924
class MandosDBusService(dbus.service.Object):
2470
@alternate_dbus_interfaces({"se.recompile.Mandos":
2471
"se.bsnet.fukt.Mandos"})
2472
class MandosDBusService(DBusObjectWithProperties):
1925
2473
"""A D-Bus proxy object"""
1926
2474
def __init__(self):
1927
dbus.service.Object.__init__(self, bus, u"/")
1928
_interface = u"se.bsnet.fukt.Mandos"
1930
@dbus.service.signal(_interface, signature=u"o")
2475
dbus.service.Object.__init__(self, bus, "/")
2476
_interface = "se.recompile.Mandos"
2478
@dbus_interface_annotations(_interface)
2480
return { "org.freedesktop.DBus.Property"
2481
".EmitsChangedSignal":
2484
@dbus.service.signal(_interface, signature="o")
1931
2485
def ClientAdded(self, objpath):
1935
@dbus.service.signal(_interface, signature=u"ss")
2489
@dbus.service.signal(_interface, signature="ss")
1936
2490
def ClientNotFound(self, fingerprint, address):
1940
@dbus.service.signal(_interface, signature=u"os")
2494
@dbus.service.signal(_interface, signature="os")
1941
2495
def ClientRemoved(self, objpath, name):
1945
@dbus.service.method(_interface, out_signature=u"ao")
2499
@dbus.service.method(_interface, out_signature="ao")
1946
2500
def GetAllClients(self):
1948
2502
return dbus.Array(c.dbus_object_path
1949
for c in tcp_server.clients)
2504
tcp_server.clients.itervalues())
1951
2506
@dbus.service.method(_interface,
1952
out_signature=u"a{oa{sv}}")
2507
out_signature="a{oa{sv}}")
1953
2508
def GetAllClientsWithProperties(self):
1955
2510
return dbus.Dictionary(
1956
((c.dbus_object_path, c.GetAll(u""))
1957
for c in tcp_server.clients),
1958
signature=u"oa{sv}")
2511
((c.dbus_object_path, c.GetAll(""))
2512
for c in tcp_server.clients.itervalues()),
1960
@dbus.service.method(_interface, in_signature=u"o")
2515
@dbus.service.method(_interface, in_signature="o")
1961
2516
def RemoveClient(self, object_path):
1963
for c in tcp_server.clients:
2518
for c in tcp_server.clients.itervalues():
1964
2519
if c.dbus_object_path == object_path:
1965
tcp_server.clients.remove(c)
2520
del tcp_server.clients[c.name]
1966
2521
c.remove_from_connection()
1967
2522
# Don't signal anything except ClientRemoved
1968
2523
c.disable(quiet=True)