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
844
class DBusObjectWithProperties(dbus.service.Object):
573
845
"""A D-Bus object with properties.
575
847
Classes inheriting from this can use the dbus_service_property
576
848
decorator to expose methods as D-Bus properties. It exposes the
577
849
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)
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),
584
def _get_all_dbus_properties(self):
862
def _get_all_dbus_things(self, thing):
585
863
"""Returns a generator of (name, attribute) pairs
587
return ((prop._dbus_name, prop)
589
inspect.getmembers(self, self._is_dbus_property))
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)))
591
873
def _get_dbus_property(self, interface_name, property_name):
592
874
"""Returns a bound method if one exists which is a D-Bus
593
875
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)):
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)
605
885
# No such property
606
raise DBusPropertyNotFound(self.dbus_object_path + u":"
607
+ interface_name + u"."
886
raise DBusPropertyNotFound(self.dbus_object_path + ":"
887
+ interface_name + "."
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
890
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
612
892
def Get(self, interface_name, property_name):
613
893
"""Standard D-Bus property Get() method, see D-Bus standard.
615
895
prop = self._get_dbus_property(interface_name, property_name)
616
if prop._dbus_access == u"write":
896
if prop._dbus_access == "write":
617
897
raise DBusPropertyAccessException(property_name)
619
if not hasattr(value, u"variant_level"):
899
if not hasattr(value, "variant_level"):
621
901
return type(value)(value, variant_level=value.variant_level+1)
623
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
903
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
624
904
def Set(self, interface_name, property_name, value):
625
905
"""Standard D-Bus property Set() method, see D-Bus standard.
627
907
prop = self._get_dbus_property(interface_name, property_name)
628
if prop._dbus_access == u"read":
908
if prop._dbus_access == "read":
629
909
raise DBusPropertyAccessException(property_name)
630
if prop._dbus_get_args_options[u"byte_arrays"]:
910
if prop._dbus_get_args_options["byte_arrays"]:
631
911
# The byte_arrays option is not supported yet on
632
912
# signatures other than "ay".
633
if prop._dbus_signature != u"ay":
913
if prop._dbus_signature != "ay":
635
value = dbus.ByteArray(''.join(unichr(byte)
915
value = dbus.ByteArray(b''.join(chr(byte)
639
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
640
out_signature=u"a{sv}")
919
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
920
out_signature="a{sv}")
641
921
def GetAll(self, interface_name):
642
922
"""Standard D-Bus property GetAll() method, see D-Bus
645
925
Note: Will not include properties with access="write".
648
for name, prop in self._get_all_dbus_properties():
928
for name, prop in self._get_all_dbus_things("property"):
649
929
if (interface_name
650
930
and interface_name != prop._dbus_interface):
651
931
# Interface non-empty but did not match
653
933
# Ignore write-only properties
654
if prop._dbus_access == u"write":
934
if prop._dbus_access == "write":
657
if not hasattr(value, u"variant_level"):
937
if not hasattr(value, "variant_level"):
938
properties[name] = value
660
all[name] = type(value)(value, variant_level=
661
value.variant_level+1)
662
return dbus.Dictionary(all, signature=u"sv")
940
properties[name] = type(value)(value, variant_level=
941
value.variant_level+1)
942
return dbus.Dictionary(properties, signature="sv")
664
944
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
666
946
path_keyword='object_path',
667
947
connection_keyword='connection')
668
948
def Introspect(self, object_path, connection):
669
"""Standard D-Bus method, overloaded to insert property tags.
949
"""Overloading of standard D-Bus method.
951
Inserts property tags and interface annotation tags.
671
953
xmlstring = dbus.service.Object.Introspect(self, object_path,
674
956
document = xml.dom.minidom.parseString(xmlstring)
675
957
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)
958
e = document.createElement("property")
959
e.setAttribute("name", name)
960
e.setAttribute("type", prop._dbus_signature)
961
e.setAttribute("access", prop._dbus_access)
681
for if_tag in document.getElementsByTagName(u"interface"):
963
for if_tag in document.getElementsByTagName("interface"):
682
965
for tag in (make_tag(document, name, prop)
684
in self._get_all_dbus_properties()
967
in self._get_all_dbus_things("property")
685
968
if prop._dbus_interface
686
== if_tag.getAttribute(u"name")):
969
== if_tag.getAttribute("name")):
687
970
if_tag.appendChild(tag)
971
# Add annotation tags
972
for typ in ("method", "signal", "property"):
973
for tag in if_tag.getElementsByTagName(typ):
975
for name, prop in (self.
976
_get_all_dbus_things(typ)):
977
if (name == tag.getAttribute("name")
978
and prop._dbus_interface
979
== if_tag.getAttribute("name")):
980
annots.update(getattr
984
for name, value in annots.iteritems():
985
ann_tag = document.createElement(
987
ann_tag.setAttribute("name", name)
988
ann_tag.setAttribute("value", value)
989
tag.appendChild(ann_tag)
990
# Add interface annotation tags
991
for annotation, value in dict(
992
itertools.chain.from_iterable(
993
annotations().iteritems()
994
for name, annotations in
995
self._get_all_dbus_things("interface")
996
if name == if_tag.getAttribute("name")
998
ann_tag = document.createElement("annotation")
999
ann_tag.setAttribute("name", annotation)
1000
ann_tag.setAttribute("value", value)
1001
if_tag.appendChild(ann_tag)
688
1002
# Add the names to the return values for the
689
1003
# "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")
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")
704
1018
document.unlink()
705
1019
except (AttributeError, xml.dom.DOMException,
706
xml.parsers.expat.ExpatError), error:
707
logger.error(u"Failed to override Introspection method",
1020
xml.parsers.expat.ExpatError) as error:
1021
logger.error("Failed to override Introspection method",
709
1023
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"})
712
1205
class ClientDBus(Client, DBusObjectWithProperties):
713
1206
"""A Client class using D-Bus
720
1213
runtime_expansions = (Client.runtime_expansions
721
+ (u"dbus_object_path",))
1214
+ ("dbus_object_path",))
723
1216
# dbus.service.Object doesn't use super(), so we can't either.
725
1218
def __init__(self, bus = None, *args, **kwargs):
726
self._approvals_pending = 0
728
1220
Client.__init__(self, *args, **kwargs)
729
1221
# Only now, when this client is initialized, can it show up on
731
1223
client_object_name = unicode(self.name).translate(
732
{ord(u"."): ord(u"_"),
733
ord(u"-"): ord(u"_")})
1224
{ord("."): ord("_"),
1225
ord("-"): ord("_")})
734
1226
self.dbus_object_path = (dbus.ObjectPath
735
(u"/clients/" + client_object_name))
1227
("/clients/" + client_object_name))
736
1228
DBusObjectWithProperties.__init__(self, self.bus,
737
1229
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))
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
783
1299
def __del__(self, *args, **kwargs):
785
1301
self.remove_from_connection()
786
1302
except LookupError:
788
if hasattr(DBusObjectWithProperties, u"__del__"):
1304
if hasattr(DBusObjectWithProperties, "__del__"):
789
1305
DBusObjectWithProperties.__del__(self, *args, **kwargs)
790
1306
Client.__del__(self, *args, **kwargs)
951
1440
# ApprovalPending - property
952
@dbus_service_property(_interface, signature=u"b", access=u"read")
1441
@dbus_service_property(_interface, signature="b", access="read")
953
1442
def ApprovalPending_dbus_property(self):
954
1443
return dbus.Boolean(bool(self.approvals_pending))
956
1445
# ApprovedByDefault - property
957
@dbus_service_property(_interface, signature=u"b",
1446
@dbus_service_property(_interface, signature="b",
959
1448
def ApprovedByDefault_dbus_property(self, value=None):
960
1449
if value is None: # get
961
1450
return dbus.Boolean(self.approved_by_default)
962
1451
self.approved_by_default = bool(value)
964
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
965
dbus.Boolean(value, variant_level=1))
967
1453
# ApprovalDelay - property
968
@dbus_service_property(_interface, signature=u"t",
1454
@dbus_service_property(_interface, signature="t",
970
1456
def ApprovalDelay_dbus_property(self, value=None):
971
1457
if value is None: # get
972
1458
return dbus.UInt64(self.approval_delay_milliseconds())
973
1459
self.approval_delay = datetime.timedelta(0, 0, 0, value)
975
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
976
dbus.UInt64(value, variant_level=1))
978
1461
# ApprovalDuration - property
979
@dbus_service_property(_interface, signature=u"t",
1462
@dbus_service_property(_interface, signature="t",
981
1464
def ApprovalDuration_dbus_property(self, value=None):
982
1465
if value is None: # get
983
return dbus.UInt64(self._timedelta_to_milliseconds(
1466
return dbus.UInt64(timedelta_to_milliseconds(
984
1467
self.approval_duration))
985
1468
self.approval_duration = datetime.timedelta(0, 0, 0, value)
987
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
988
dbus.UInt64(value, variant_level=1))
990
1470
# Name - property
991
@dbus_service_property(_interface, signature=u"s", access=u"read")
1471
@dbus_service_property(_interface, signature="s", access="read")
992
1472
def Name_dbus_property(self):
993
1473
return dbus.String(self.name)
995
1475
# Fingerprint - property
996
@dbus_service_property(_interface, signature=u"s", access=u"read")
1476
@dbus_service_property(_interface, signature="s", access="read")
997
1477
def Fingerprint_dbus_property(self):
998
1478
return dbus.String(self.fingerprint)
1000
1480
# Host - property
1001
@dbus_service_property(_interface, signature=u"s",
1002
access=u"readwrite")
1481
@dbus_service_property(_interface, signature="s",
1003
1483
def Host_dbus_property(self, value=None):
1004
1484
if value is None: # get
1005
1485
return dbus.String(self.host)
1008
self.PropertyChanged(dbus.String(u"Host"),
1009
dbus.String(value, variant_level=1))
1486
self.host = unicode(value)
1011
1488
# Created - property
1012
@dbus_service_property(_interface, signature=u"s", access=u"read")
1489
@dbus_service_property(_interface, signature="s", access="read")
1013
1490
def Created_dbus_property(self):
1014
return dbus.String(self._datetime_to_dbus(self.created))
1491
return datetime_to_dbus(self.created)
1016
1493
# LastEnabled - property
1017
@dbus_service_property(_interface, signature=u"s", access=u"read")
1494
@dbus_service_property(_interface, signature="s", access="read")
1018
1495
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))
1496
return datetime_to_dbus(self.last_enabled)
1023
1498
# Enabled - property
1024
@dbus_service_property(_interface, signature=u"b",
1025
access=u"readwrite")
1499
@dbus_service_property(_interface, signature="b",
1026
1501
def Enabled_dbus_property(self, value=None):
1027
1502
if value is None: # get
1028
1503
return dbus.Boolean(self.enabled)
1034
1509
# LastCheckedOK - property
1035
@dbus_service_property(_interface, signature=u"s",
1036
access=u"readwrite")
1510
@dbus_service_property(_interface, signature="s",
1037
1512
def LastCheckedOK_dbus_property(self, value=None):
1038
1513
if value is not None:
1039
1514
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
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)
1046
1529
# LastApprovalRequest - property
1047
@dbus_service_property(_interface, signature=u"s", access=u"read")
1530
@dbus_service_property(_interface, signature="s", access="read")
1048
1531
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))
1532
return datetime_to_dbus(self.last_approval_request)
1055
1534
# Timeout - property
1056
@dbus_service_property(_interface, signature=u"t",
1057
access=u"readwrite")
1535
@dbus_service_property(_interface, signature="t",
1058
1537
def Timeout_dbus_property(self, value=None):
1059
1538
if value is None: # get
1060
1539
return dbus.UInt64(self.timeout_milliseconds())
1540
old_timeout = self.timeout
1061
1541
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))
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)
1083
1567
# Interval - property
1084
@dbus_service_property(_interface, signature=u"t",
1085
access=u"readwrite")
1568
@dbus_service_property(_interface, signature="t",
1086
1570
def Interval_dbus_property(self, value=None):
1087
1571
if value is None: # get
1088
1572
return dbus.UInt64(self.interval_milliseconds())
1089
1573
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:
1574
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
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
1101
1583
# Checker - property
1102
@dbus_service_property(_interface, signature=u"s",
1103
access=u"readwrite")
1584
@dbus_service_property(_interface, signature="s",
1104
1586
def Checker_dbus_property(self, value=None):
1105
1587
if value is None: # get
1106
1588
return dbus.String(self.checker_command)
1107
self.checker_command = value
1109
self.PropertyChanged(dbus.String(u"Checker"),
1110
dbus.String(self.checker_command,
1589
self.checker_command = unicode(value)
1113
1591
# CheckerRunning - property
1114
@dbus_service_property(_interface, signature=u"b",
1115
access=u"readwrite")
1592
@dbus_service_property(_interface, signature="b",
1116
1594
def CheckerRunning_dbus_property(self, value=None):
1117
1595
if value is None: # get
1118
1596
return dbus.Boolean(self.checker is not None)
1170
1648
def handle(self):
1171
1649
with contextlib.closing(self.server.child_pipe) as child_pipe:
1172
logger.info(u"TCP connection from: %s",
1650
logger.info("TCP connection from: %s",
1173
1651
unicode(self.client_address))
1174
logger.debug(u"Pipe FD: %d",
1652
logger.debug("Pipe FD: %d",
1175
1653
self.server.child_pipe.fileno())
1177
1655
session = (gnutls.connection
1178
1656
.ClientSession(self.request,
1179
1657
gnutls.connection
1180
1658
.X509Credentials()))
1182
1660
# Note: gnutls.connection.X509Credentials is really a
1183
1661
# generic GnuTLS certificate credentials object so long as
1184
1662
# no X.509 keys are added to it. Therefore, we can use it
1185
1663
# 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",
1665
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1666
# "+AES-256-CBC", "+SHA1",
1667
# "+COMP-NULL", "+CTYPE-OPENPGP",
1191
1669
# Use a fallback default, since this MUST be set.
1192
1670
priority = self.server.gnutls_priority
1193
1671
if priority is None:
1194
priority = u"NORMAL"
1195
1673
(gnutls.library.functions
1196
1674
.gnutls_priority_set_direct(session._c_object,
1197
1675
priority, None))
1199
1677
# Start communication using the Mandos protocol
1200
1678
# Get protocol number
1201
1679
line = self.request.makefile().readline()
1202
logger.debug(u"Protocol version: %r", line)
1680
logger.debug("Protocol version: %r", line)
1204
1682
if int(line.strip().split()[0]) > 1:
1205
1683
raise RuntimeError
1206
except (ValueError, IndexError, RuntimeError), error:
1207
logger.error(u"Unknown protocol version: %s", error)
1684
except (ValueError, IndexError, RuntimeError) as error:
1685
logger.error("Unknown protocol version: %s", error)
1210
1688
# Start GnuTLS connection
1212
1690
session.handshake()
1213
except gnutls.errors.GNUTLSError, error:
1214
logger.warning(u"Handshake failed: %s", error)
1691
except gnutls.errors.GNUTLSError as error:
1692
logger.warning("Handshake failed: %s", error)
1215
1693
# Do not run session.bye() here: the session is not
1216
1694
# established. Just abandon the request.
1218
logger.debug(u"Handshake succeeded")
1696
logger.debug("Handshake succeeded")
1220
1698
approval_required = False
1223
1701
fpr = self.fingerprint(self.peer_certificate
1225
except (TypeError, gnutls.errors.GNUTLSError), error:
1226
logger.warning(u"Bad certificate: %s", error)
1704
gnutls.errors.GNUTLSError) as error:
1705
logger.warning("Bad certificate: %s", error)
1228
logger.debug(u"Fingerprint: %s", fpr)
1707
logger.debug("Fingerprint: %s", fpr)
1231
1710
client = ProxyClient(child_pipe, fpr,
1232
1711
self.client_address)
1672
2139
##################################################################
1673
2140
# 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]
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()
1704
2180
if options.check:
1753
2232
##################################################################
1755
2234
# 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":
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":
1762
2252
syslogger.setFormatter(logging.Formatter
1763
(u'Mandos (%s) [%%(process)d]:'
1764
u' %%(levelname)s: %%(message)s'
1765
% server_settings[u"servicename"]))
2253
('Mandos ({0}) [%(process)d]:'
2254
' %(levelname)s: %(message)s'
2255
.format(server_settings
1767
2258
# 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"],
2259
client_config = configparser.SafeConfigParser(Client
2261
client_config.read(os.path.join(server_settings["configdir"],
1779
2264
global mandos_dbus_service
1780
2265
mandos_dbus_service = None
1782
tcp_server = MandosServer((server_settings[u"address"],
1783
server_settings[u"port"]),
2267
tcp_server = MandosServer((server_settings["address"],
2268
server_settings["port"]),
1785
interface=(server_settings[u"interface"]
2270
interface=(server_settings["interface"]
1787
2272
use_ipv6=use_ipv6,
1788
2273
gnutls_priority=
1789
server_settings[u"priority"],
2274
server_settings["priority"],
1790
2275
use_dbus=use_dbus)
1792
pidfilename = u"/var/run/mandos.pid"
2277
pidfilename = "/var/run/mandos.pid"
1794
pidfile = open(pidfilename, u"w")
1796
logger.error(u"Could not open file %r", pidfilename)
2279
pidfile = open(pidfilename, "w")
2280
except IOError as e:
2281
logger.error("Could not open file %r", pidfilename,
1799
uid = pwd.getpwnam(u"_mandos").pw_uid
1800
gid = pwd.getpwnam(u"_mandos").pw_gid
2284
for name in ("_mandos", "mandos", "nobody"):
1803
uid = pwd.getpwnam(u"mandos").pw_uid
1804
gid = pwd.getpwnam(u"mandos").pw_gid
2286
uid = pwd.getpwnam(name).pw_uid
2287
gid = pwd.getpwnam(name).pw_gid
1805
2289
except KeyError:
1807
uid = pwd.getpwnam(u"nobody").pw_uid
1808
gid = pwd.getpwnam(u"nobody").pw_gid
1815
except OSError, error:
2297
except OSError as error:
1816
2298
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)
1828
2302
# Enable all possible GnuTLS debugging
1834
2308
@gnutls.library.types.gnutls_log_func
1835
2309
def debug_gnutls(level, string):
1836
logger.debug(u"GnuTLS: %s", string[:-1])
2310
logger.debug("GnuTLS: %s", string[:-1])
1838
2312
(gnutls.library.functions
1839
2313
.gnutls_global_set_log_function(debug_gnutls))
1841
2315
# Redirect stdin so all checkers get /dev/null
1842
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2316
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1843
2317
os.dup2(null, sys.stdin.fileno())
1847
# No console logging
1848
logger.removeHandler(console)
2321
# Need to fork before connecting to D-Bus
2323
# Close all input and output, do double fork, etc.
2326
gobject.threads_init()
1851
2328
global main_loop
1852
2329
# From the Avahi example code
1853
DBusGMainLoop(set_as_default=True )
2330
DBusGMainLoop(set_as_default=True)
1854
2331
main_loop = gobject.MainLoop()
1855
2332
bus = dbus.SystemBus()
1856
2333
# End of Avahi example code
1859
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2336
bus_name = dbus.service.BusName("se.recompile.Mandos",
1860
2337
bus, do_not_queue=True)
1861
except dbus.exceptions.NameExistsException, e:
1862
logger.error(unicode(e) + u", disabling D-Bus")
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)
1863
2343
use_dbus = False
1864
server_settings[u"use_dbus"] = False
2344
server_settings["use_dbus"] = False
1865
2345
tcp_server.use_dbus = False
1866
2346
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)
2347
service = AvahiServiceToSyslog(name =
2348
server_settings["servicename"],
2349
servicetype = "_mandos._tcp",
2350
protocol = protocol, bus = bus)
1870
2351
if server_settings["interface"]:
1871
2352
service.interface = (if_nametoindex
1872
(str(server_settings[u"interface"])))
1875
# Close all input and output, do double fork, etc.
2353
(str(server_settings["interface"])))
1878
2355
global multiprocessing_manager
1879
2356
multiprocessing_manager = multiprocessing.Manager()
1881
2358
client_class = Client
1883
2360
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):
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))
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()))
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)
1901
2456
if not tcp_server.clients:
1902
logger.warning(u"No clients defined")
2457
logger.warning("No clients defined")
1907
2462
pid = os.getpid()
1908
pidfile.write(str(pid) + "\n")
2463
pidfile.write(str(pid) + "\n".encode("utf-8"))
1910
2465
except IOError:
1911
logger.error(u"Could not write to file %r with PID %d",
2466
logger.error("Could not write to file %r with PID %d",
1912
2467
pidfilename, pid)
1913
2468
except NameError:
1914
2469
# "pidfile" was never created
1916
2471
del pidfilename
1918
2472
signal.signal(signal.SIGINT, signal.SIG_IGN)
1920
2474
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1921
2475
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1924
class MandosDBusService(dbus.service.Object):
2478
@alternate_dbus_interfaces({"se.recompile.Mandos":
2479
"se.bsnet.fukt.Mandos"})
2480
class MandosDBusService(DBusObjectWithProperties):
1925
2481
"""A D-Bus proxy object"""
1926
2482
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")
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")
1931
2493
def ClientAdded(self, objpath):
1935
@dbus.service.signal(_interface, signature=u"ss")
2497
@dbus.service.signal(_interface, signature="ss")
1936
2498
def ClientNotFound(self, fingerprint, address):
1940
@dbus.service.signal(_interface, signature=u"os")
2502
@dbus.service.signal(_interface, signature="os")
1941
2503
def ClientRemoved(self, objpath, name):
1945
@dbus.service.method(_interface, out_signature=u"ao")
2507
@dbus.service.method(_interface, out_signature="ao")
1946
2508
def GetAllClients(self):
1948
2510
return dbus.Array(c.dbus_object_path
1949
for c in tcp_server.clients)
2512
tcp_server.clients.itervalues())
1951
2514
@dbus.service.method(_interface,
1952
out_signature=u"a{oa{sv}}")
2515
out_signature="a{oa{sv}}")
1953
2516
def GetAllClientsWithProperties(self):
1955
2518
return dbus.Dictionary(
1956
((c.dbus_object_path, c.GetAll(u""))
1957
for c in tcp_server.clients),
1958
signature=u"oa{sv}")
2519
((c.dbus_object_path, c.GetAll(""))
2520
for c in tcp_server.clients.itervalues()),
1960
@dbus.service.method(_interface, in_signature=u"o")
2523
@dbus.service.method(_interface, in_signature="o")
1961
2524
def RemoveClient(self, object_path):
1963
for c in tcp_server.clients:
2526
for c in tcp_server.clients.itervalues():
1964
2527
if c.dbus_object_path == object_path:
1965
tcp_server.clients.remove(c)
2528
del tcp_server.clients[c.name]
1966
2529
c.remove_from_connection()
1967
2530
# Don't signal anything except ClientRemoved
1968
2531
c.disable(quiet=True)