86
80
except ImportError:
87
81
SO_BINDTODEVICE = None
90
stored_state_file = "clients.pickle"
92
logger = logging.getLogger()
86
logger = logging.Logger(u'mandos')
93
87
syslogger = (logging.handlers.SysLogHandler
94
88
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
95
address = str("/dev/log")))
98
if_nametoindex = (ctypes.cdll.LoadLibrary
99
(ctypes.util.find_library("c"))
101
except (OSError, AttributeError):
102
def if_nametoindex(interface):
103
"Get an interface index the hard way, i.e. using fcntl()"
104
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
105
with contextlib.closing(socket.socket()) as s:
106
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
107
struct.pack(str("16s16x"),
109
interface_index = struct.unpack(str("I"),
111
return interface_index
114
def initlogger(debug, level=logging.WARNING):
115
"""init logger and add loglevel"""
117
syslogger.setFormatter(logging.Formatter
118
('Mandos [%(process)d]: %(levelname)s:'
120
logger.addHandler(syslogger)
123
console = logging.StreamHandler()
124
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
128
logger.addHandler(console)
129
logger.setLevel(level)
132
class PGPError(Exception):
133
"""Exception if encryption/decryption fails"""
137
class PGPEngine(object):
138
"""A simple class for OpenPGP symmetric encryption & decryption"""
140
self.gnupg = GnuPGInterface.GnuPG()
141
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
142
self.gnupg = GnuPGInterface.GnuPG()
143
self.gnupg.options.meta_interactive = False
144
self.gnupg.options.homedir = self.tempdir
145
self.gnupg.options.extra_args.extend(['--force-mdc',
152
def __exit__ (self, exc_type, exc_value, traceback):
160
if self.tempdir is not None:
161
# Delete contents of tempdir
162
for root, dirs, files in os.walk(self.tempdir,
164
for filename in files:
165
os.remove(os.path.join(root, filename))
167
os.rmdir(os.path.join(root, dirname))
169
os.rmdir(self.tempdir)
172
def password_encode(self, password):
173
# Passphrase can not be empty and can not contain newlines or
174
# NUL bytes. So we prefix it and hex encode it.
175
return b"mandos" + binascii.hexlify(password)
177
def encrypt(self, data, password):
178
self.gnupg.passphrase = self.password_encode(password)
179
with open(os.devnull, "w") as devnull:
181
proc = self.gnupg.run(['--symmetric'],
182
create_fhs=['stdin', 'stdout'],
183
attach_fhs={'stderr': devnull})
184
with contextlib.closing(proc.handles['stdin']) as f:
186
with contextlib.closing(proc.handles['stdout']) as f:
187
ciphertext = f.read()
191
self.gnupg.passphrase = None
194
def decrypt(self, data, password):
195
self.gnupg.passphrase = self.password_encode(password)
196
with open(os.devnull, "w") as devnull:
198
proc = self.gnupg.run(['--decrypt'],
199
create_fhs=['stdin', 'stdout'],
200
attach_fhs={'stderr': devnull})
201
with contextlib.closing(proc.handles['stdin']) as f:
203
with contextlib.closing(proc.handles['stdout']) as f:
204
decrypted_plaintext = f.read()
208
self.gnupg.passphrase = None
209
return decrypted_plaintext
89
address = "/dev/log"))
90
syslogger.setFormatter(logging.Formatter
91
(u'Mandos [%(process)d]: %(levelname)s:'
93
logger.addHandler(syslogger)
95
console = logging.StreamHandler()
96
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
99
logger.addHandler(console)
213
101
class AvahiError(Exception):
214
102
def __init__(self, value, *args, **kwargs):
312
193
self.group.Commit()
313
194
def entry_group_state_changed(self, state, error):
314
195
"""Derived from the Avahi example code"""
315
logger.debug("Avahi entry group state change: %i", state)
196
logger.debug(u"Avahi state change: %i", state)
317
198
if state == avahi.ENTRY_GROUP_ESTABLISHED:
318
logger.debug("Zeroconf service established.")
199
logger.debug(u"Zeroconf service established.")
319
200
elif state == avahi.ENTRY_GROUP_COLLISION:
320
logger.info("Zeroconf service name collision.")
201
logger.warning(u"Zeroconf service name collision.")
322
203
elif state == avahi.ENTRY_GROUP_FAILURE:
323
logger.critical("Avahi: Error in group state changed %s",
204
logger.critical(u"Avahi: Error in group state changed %s",
325
raise AvahiGroupError("State changed: %s"
206
raise AvahiGroupError(u"State changed: %s"
326
207
% unicode(error))
327
208
def cleanup(self):
328
209
"""Derived from the Avahi example code"""
329
210
if self.group is not None:
332
except (dbus.exceptions.UnknownMethodException,
333
dbus.exceptions.DBusException):
335
212
self.group = None
337
def server_state_changed(self, state, error=None):
213
def server_state_changed(self, state):
338
214
"""Derived from the Avahi example code"""
339
logger.debug("Avahi server state change: %i", state)
340
bad_states = { avahi.SERVER_INVALID:
341
"Zeroconf server invalid",
342
avahi.SERVER_REGISTERING: None,
343
avahi.SERVER_COLLISION:
344
"Zeroconf server name collision",
345
avahi.SERVER_FAILURE:
346
"Zeroconf server failure" }
347
if state in bad_states:
348
if bad_states[state] is not None:
350
logger.error(bad_states[state])
352
logger.error(bad_states[state] + ": %r", error)
215
if state == avahi.SERVER_COLLISION:
216
logger.error(u"Zeroconf server name collision")
354
218
elif state == avahi.SERVER_RUNNING:
358
logger.debug("Unknown state: %r", state)
360
logger.debug("Unknown state: %r: %r", state, error)
361
220
def activate(self):
362
221
"""Derived from the Avahi example code"""
363
222
if self.server is None:
364
223
self.server = dbus.Interface(
365
224
self.bus.get_object(avahi.DBUS_NAME,
366
avahi.DBUS_PATH_SERVER,
367
follow_name_owner_changes=True),
225
avahi.DBUS_PATH_SERVER),
368
226
avahi.DBUS_INTERFACE_SERVER)
369
self.server.connect_to_signal("StateChanged",
227
self.server.connect_to_signal(u"StateChanged",
370
228
self.server_state_changed)
371
229
self.server_state_changed(self.server.GetState())
373
class AvahiServiceToSyslog(AvahiService):
375
"""Add the new name to the syslog messages"""
376
ret = AvahiService.rename(self)
377
syslogger.setFormatter(logging.Formatter
378
('Mandos (%s) [%%(process)d]:'
379
' %%(levelname)s: %%(message)s'
383
def timedelta_to_milliseconds(td):
384
"Convert a datetime.timedelta() to milliseconds"
385
return ((td.days * 24 * 60 * 60 * 1000)
386
+ (td.seconds * 1000)
387
+ (td.microseconds // 1000))
389
232
class Client(object):
390
233
"""A representation of a client host served by this server.
393
approved: bool(); 'None' if not yet approved/disapproved
394
approval_delay: datetime.timedelta(); Time to wait for approval
395
approval_duration: datetime.timedelta(); Duration of one approval
236
name: string; from the config file, used in log messages and
238
fingerprint: string (40 or 32 hexadecimal digits); used to
239
uniquely identify the client
240
secret: bytestring; sent verbatim (over TLS) to client
241
host: string; available for use by the checker command
242
created: datetime.datetime(); (UTC) object creation
243
last_enabled: datetime.datetime(); (UTC)
245
last_checked_ok: datetime.datetime(); (UTC) or None
246
timeout: datetime.timedelta(); How long from last_checked_ok
247
until this client is disabled
248
interval: datetime.timedelta(); How often to start a new checker
249
disable_hook: If set, called by disable() as disable_hook(self)
396
250
checker: subprocess.Popen(); a running checker process used
397
251
to see if the client lives.
398
252
'None' if no process is running.
399
checker_callback_tag: a gobject event source tag, or None
400
checker_command: string; External command which is run to check
401
if client lives. %() expansions are done at
253
checker_initiator_tag: a gobject event source tag, or None
254
disable_initiator_tag: - '' -
255
checker_callback_tag: - '' -
256
checker_command: string; External command which is run to check if
257
client lives. %() expansions are done at
402
258
runtime with vars(self) as dict, so that for
403
259
instance %(name)s can be used in the command.
404
checker_initiator_tag: a gobject event source tag, or None
405
created: datetime.datetime(); (UTC) object creation
406
client_structure: Object describing what attributes a client has
407
and is used for storing the client at exit
408
260
current_checker_command: string; current running checker_command
409
disable_initiator_tag: a gobject event source tag, or None
411
fingerprint: string (40 or 32 hexadecimal digits); used to
412
uniquely identify the client
413
host: string; available for use by the checker command
414
interval: datetime.timedelta(); How often to start a new checker
415
last_approval_request: datetime.datetime(); (UTC) or None
416
last_checked_ok: datetime.datetime(); (UTC) or None
417
last_checker_status: integer between 0 and 255 reflecting exit
418
status of last checker. -1 reflects crashed
419
checker, -2 means no checker completed yet.
420
last_enabled: datetime.datetime(); (UTC) or None
421
name: string; from the config file, used in log messages and
423
secret: bytestring; sent verbatim (over TLS) to client
424
timeout: datetime.timedelta(); How long from last_checked_ok
425
until this client is disabled
426
extended_timeout: extra long timeout when secret has been sent
427
runtime_expansions: Allowed attributes for runtime expansion.
428
expires: datetime.datetime(); time (UTC) when a client will be
261
approved_delay: datetime.timedelta(); Time to wait for approval
262
_approved: bool(); 'None' if not yet approved/disapproved
263
approved_duration: datetime.timedelta(); Duration of one approval
432
runtime_expansions = ("approval_delay", "approval_duration",
433
"created", "enabled", "fingerprint",
434
"host", "interval", "last_checked_ok",
435
"last_enabled", "name", "timeout")
436
client_defaults = { "timeout": "5m",
437
"extended_timeout": "15m",
439
"checker": "fping -q -- %%(host)s",
441
"approval_delay": "0s",
442
"approval_duration": "1s",
443
"approved_by_default": "True",
267
def _timedelta_to_milliseconds(td):
268
"Convert a datetime.timedelta() to milliseconds"
269
return ((td.days * 24 * 60 * 60 * 1000)
270
+ (td.seconds * 1000)
271
+ (td.microseconds // 1000))
447
273
def timeout_milliseconds(self):
448
274
"Return the 'timeout' attribute in milliseconds"
449
return timedelta_to_milliseconds(self.timeout)
451
def extended_timeout_milliseconds(self):
452
"Return the 'extended_timeout' attribute in milliseconds"
453
return timedelta_to_milliseconds(self.extended_timeout)
275
return self._timedelta_to_milliseconds(self.timeout)
455
277
def interval_milliseconds(self):
456
278
"Return the 'interval' attribute in milliseconds"
457
return timedelta_to_milliseconds(self.interval)
279
return self._timedelta_to_milliseconds(self.interval)
281
def approved_delay_milliseconds(self):
282
return self._timedelta_to_milliseconds(self.approved_delay)
459
def approval_delay_milliseconds(self):
460
return timedelta_to_milliseconds(self.approval_delay)
463
def config_parser(config):
464
"""Construct a new dict of client settings of this form:
465
{ client_name: {setting_name: value, ...}, ...}
466
with exceptions for any special settings as defined above.
467
NOTE: Must be a pure function. Must return the same result
468
value given the same arguments.
471
for client_name in config.sections():
472
section = dict(config.items(client_name))
473
client = settings[client_name] = {}
475
client["host"] = section["host"]
476
# Reformat values from string types to Python types
477
client["approved_by_default"] = config.getboolean(
478
client_name, "approved_by_default")
479
client["enabled"] = config.getboolean(client_name,
482
client["fingerprint"] = (section["fingerprint"].upper()
484
if "secret" in section:
485
client["secret"] = section["secret"].decode("base64")
486
elif "secfile" in section:
487
with open(os.path.expanduser(os.path.expandvars
488
(section["secfile"])),
490
client["secret"] = secfile.read()
492
raise TypeError("No secret or secfile for section %s"
494
client["timeout"] = string_to_delta(section["timeout"])
495
client["extended_timeout"] = string_to_delta(
496
section["extended_timeout"])
497
client["interval"] = string_to_delta(section["interval"])
498
client["approval_delay"] = string_to_delta(
499
section["approval_delay"])
500
client["approval_duration"] = string_to_delta(
501
section["approval_duration"])
502
client["checker_command"] = section["checker"]
503
client["last_approval_request"] = None
504
client["last_checked_ok"] = None
505
client["last_checker_status"] = -2
510
def __init__(self, settings, name = None):
284
def __init__(self, name = None, disable_hook=None, config=None):
511
285
"""Note: the 'checker' key in 'config' sets the
512
286
'checker_command' attribute and *not* the 'checker'
515
# adding all client settings
516
for setting, value in settings.iteritems():
517
setattr(self, setting, value)
520
if not hasattr(self, "last_enabled"):
521
self.last_enabled = datetime.datetime.utcnow()
522
if not hasattr(self, "expires"):
523
self.expires = (datetime.datetime.utcnow()
526
self.last_enabled = None
529
logger.debug("Creating client %r", self.name)
291
logger.debug(u"Creating client %r", self.name)
530
292
# Uppercase and remove spaces from fingerprint for later
531
293
# comparison purposes with return value from the fingerprint()
533
logger.debug(" Fingerprint: %s", self.fingerprint)
534
self.created = settings.get("created",
535
datetime.datetime.utcnow())
537
# attributes specific for this server instance
295
self.fingerprint = (config[u"fingerprint"].upper()
297
logger.debug(u" Fingerprint: %s", self.fingerprint)
298
if u"secret" in config:
299
self.secret = config[u"secret"].decode(u"base64")
300
elif u"secfile" in config:
301
with open(os.path.expanduser(os.path.expandvars
302
(config[u"secfile"])),
304
self.secret = secfile.read()
306
#XXX Need to allow secret on demand!
307
raise TypeError(u"No secret or secfile for client %s"
309
self.host = config.get(u"host", u"")
310
self.created = datetime.datetime.utcnow()
312
self.last_enabled = None
313
self.last_checked_ok = None
314
self.timeout = string_to_delta(config[u"timeout"])
315
self.interval = string_to_delta(config[u"interval"])
316
self.disable_hook = disable_hook
538
317
self.checker = None
539
318
self.checker_initiator_tag = None
540
319
self.disable_initiator_tag = None
541
320
self.checker_callback_tag = None
321
self.checker_command = config[u"checker"]
542
322
self.current_checker_command = None
323
self.last_connect = None
544
324
self.approvals_pending = 0
545
self.changedstate = (multiprocessing_manager
546
.Condition(multiprocessing_manager
548
self.client_structure = [attr for attr in
549
self.__dict__.iterkeys()
550
if not attr.startswith("_")]
551
self.client_structure.append("client_structure")
553
for name, t in inspect.getmembers(type(self),
557
if not name.startswith("_"):
558
self.client_structure.append(name)
560
# Send notice to process children that client state has changed
325
self._approved = None
326
self.approved_by_default = config.get(u"approved_by_default",
328
self.approved_delay = string_to_delta(
329
config[u"approved_delay"])
330
self.approved_duration = string_to_delta(
331
config[u"approved_duration"])
332
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
561
334
def send_changedstate(self):
562
with self.changedstate:
563
self.changedstate.notify_all()
335
self.changedstate.acquire()
336
self.changedstate.notify_all()
337
self.changedstate.release()
565
339
def enable(self):
566
340
"""Start this client's checker and timeout hooks"""
567
if getattr(self, "enabled", False):
341
if getattr(self, u"enabled", False):
568
342
# Already enabled
570
344
self.send_changedstate()
571
self.expires = datetime.datetime.utcnow() + self.timeout
573
345
self.last_enabled = datetime.datetime.utcnow()
576
def disable(self, quiet=True):
577
"""Disable this client."""
578
if not getattr(self, "enabled", False):
581
self.send_changedstate()
583
logger.info("Disabling client %s", self.name)
584
if getattr(self, "disable_initiator_tag", False):
585
gobject.source_remove(self.disable_initiator_tag)
586
self.disable_initiator_tag = None
588
if getattr(self, "checker_initiator_tag", False):
589
gobject.source_remove(self.checker_initiator_tag)
590
self.checker_initiator_tag = None
593
# Do not run this again if called by a gobject.timeout_add
599
def init_checker(self):
600
346
# Schedule a new checker to be started an 'interval' from now,
601
347
# and every interval from then on.
602
348
self.checker_initiator_tag = (gobject.timeout_add
606
352
self.disable_initiator_tag = (gobject.timeout_add
607
353
(self.timeout_milliseconds(),
609
356
# Also start a new checker *right now*.
610
357
self.start_checker()
359
def disable(self, quiet=True):
360
"""Disable this client."""
361
if not getattr(self, "enabled", False):
364
self.send_changedstate()
366
logger.info(u"Disabling client %s", self.name)
367
if getattr(self, u"disable_initiator_tag", False):
368
gobject.source_remove(self.disable_initiator_tag)
369
self.disable_initiator_tag = None
370
if getattr(self, u"checker_initiator_tag", False):
371
gobject.source_remove(self.checker_initiator_tag)
372
self.checker_initiator_tag = None
374
if self.disable_hook:
375
self.disable_hook(self)
377
# Do not run this again if called by a gobject.timeout_add
381
self.disable_hook = None
612
384
def checker_callback(self, pid, condition, command):
613
385
"""The checker has completed, so take appropriate actions."""
614
386
self.checker_callback_tag = None
615
387
self.checker = None
616
388
if os.WIFEXITED(condition):
617
self.last_checker_status = os.WEXITSTATUS(condition)
618
if self.last_checker_status == 0:
619
logger.info("Checker for %(name)s succeeded",
389
exitstatus = os.WEXITSTATUS(condition)
391
logger.info(u"Checker for %(name)s succeeded",
621
393
self.checked_ok()
623
logger.info("Checker for %(name)s failed",
395
logger.info(u"Checker for %(name)s failed",
626
self.last_checker_status = -1
627
logger.warning("Checker for %(name)s crashed?",
398
logger.warning(u"Checker for %(name)s crashed?",
630
401
def checked_ok(self):
631
"""Assert that the client has been seen, alive and well."""
402
"""Bump up the timeout for this client.
404
This should only be called when the client has been seen,
632
407
self.last_checked_ok = datetime.datetime.utcnow()
633
self.last_checker_status = 0
636
def bump_timeout(self, timeout=None):
637
"""Bump up the timeout for this client."""
639
timeout = self.timeout
640
if self.disable_initiator_tag is not None:
641
gobject.source_remove(self.disable_initiator_tag)
642
if getattr(self, "enabled", False):
643
self.disable_initiator_tag = (gobject.timeout_add
644
(timedelta_to_milliseconds
645
(timeout), self.disable))
646
self.expires = datetime.datetime.utcnow() + timeout
648
def need_approval(self):
649
self.last_approval_request = datetime.datetime.utcnow()
408
gobject.source_remove(self.disable_initiator_tag)
409
self.disable_initiator_tag = (gobject.timeout_add
410
(self.timeout_milliseconds(),
651
413
def start_checker(self):
652
414
"""Start a new checker subprocess if one is not running.
813
553
class DBusObjectWithProperties(dbus.service.Object):
814
554
"""A D-Bus object with properties.
816
556
Classes inheriting from this can use the dbus_service_property
817
557
decorator to expose methods as D-Bus properties. It exposes the
818
558
standard Get(), Set(), and GetAll() methods on the D-Bus.
822
def _is_dbus_thing(thing):
823
"""Returns a function testing if an attribute is a D-Bus thing
825
If called like _is_dbus_thing("method") it returns a function
826
suitable for use as predicate to inspect.getmembers().
828
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
562
def _is_dbus_property(obj):
563
return getattr(obj, u"_dbus_is_property", False)
831
def _get_all_dbus_things(self, thing):
565
def _get_all_dbus_properties(self):
832
566
"""Returns a generator of (name, attribute) pairs
834
return ((athing.__get__(self)._dbus_name,
835
athing.__get__(self))
836
for cls in self.__class__.__mro__
838
inspect.getmembers(cls,
839
self._is_dbus_thing(thing)))
568
return ((prop._dbus_name, prop)
570
inspect.getmembers(self, self._is_dbus_property))
841
572
def _get_dbus_property(self, interface_name, property_name):
842
573
"""Returns a bound method if one exists which is a D-Bus
843
574
property with the specified name and interface.
845
for cls in self.__class__.__mro__:
846
for name, value in (inspect.getmembers
848
self._is_dbus_thing("property"))):
849
if (value._dbus_name == property_name
850
and value._dbus_interface == interface_name):
851
return value.__get__(self)
576
for name in (property_name,
577
property_name + u"_dbus_property"):
578
prop = getattr(self, name, None)
580
or not self._is_dbus_property(prop)
581
or prop._dbus_name != property_name
582
or (interface_name and prop._dbus_interface
583
and interface_name != prop._dbus_interface)):
853
586
# No such property
854
raise DBusPropertyNotFound(self.dbus_object_path + ":"
855
+ interface_name + "."
587
raise DBusPropertyNotFound(self.dbus_object_path + u":"
588
+ interface_name + u"."
858
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
591
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
860
593
def Get(self, interface_name, property_name):
861
594
"""Standard D-Bus property Get() method, see D-Bus standard.
863
596
prop = self._get_dbus_property(interface_name, property_name)
864
if prop._dbus_access == "write":
597
if prop._dbus_access == u"write":
865
598
raise DBusPropertyAccessException(property_name)
867
if not hasattr(value, "variant_level"):
600
if not hasattr(value, u"variant_level"):
869
602
return type(value)(value, variant_level=value.variant_level+1)
871
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
604
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
872
605
def Set(self, interface_name, property_name, value):
873
606
"""Standard D-Bus property Set() method, see D-Bus standard.
875
608
prop = self._get_dbus_property(interface_name, property_name)
876
if prop._dbus_access == "read":
609
if prop._dbus_access == u"read":
877
610
raise DBusPropertyAccessException(property_name)
878
if prop._dbus_get_args_options["byte_arrays"]:
611
if prop._dbus_get_args_options[u"byte_arrays"]:
879
612
# The byte_arrays option is not supported yet on
880
613
# signatures other than "ay".
881
if prop._dbus_signature != "ay":
614
if prop._dbus_signature != u"ay":
883
value = dbus.ByteArray(b''.join(chr(byte)
616
value = dbus.ByteArray(''.join(unichr(byte)
887
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
888
out_signature="a{sv}")
620
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
621
out_signature=u"a{sv}")
889
622
def GetAll(self, interface_name):
890
623
"""Standard D-Bus property GetAll() method, see D-Bus
893
626
Note: Will not include properties with access="write".
896
for name, prop in self._get_all_dbus_things("property"):
629
for name, prop in self._get_all_dbus_properties():
897
630
if (interface_name
898
631
and interface_name != prop._dbus_interface):
899
632
# Interface non-empty but did not match
901
634
# Ignore write-only properties
902
if prop._dbus_access == "write":
635
if prop._dbus_access == u"write":
905
if not hasattr(value, "variant_level"):
906
properties[name] = value
638
if not hasattr(value, u"variant_level"):
908
properties[name] = type(value)(value, variant_level=
909
value.variant_level+1)
910
return dbus.Dictionary(properties, signature="sv")
641
all[name] = type(value)(value, variant_level=
642
value.variant_level+1)
643
return dbus.Dictionary(all, signature=u"sv")
912
645
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
914
647
path_keyword='object_path',
915
648
connection_keyword='connection')
916
649
def Introspect(self, object_path, connection):
917
"""Overloading of standard D-Bus method.
919
Inserts property tags and interface annotation tags.
650
"""Standard D-Bus method, overloaded to insert property tags.
921
652
xmlstring = dbus.service.Object.Introspect(self, object_path,
924
655
document = xml.dom.minidom.parseString(xmlstring)
925
656
def make_tag(document, name, prop):
926
e = document.createElement("property")
927
e.setAttribute("name", name)
928
e.setAttribute("type", prop._dbus_signature)
929
e.setAttribute("access", prop._dbus_access)
657
e = document.createElement(u"property")
658
e.setAttribute(u"name", name)
659
e.setAttribute(u"type", prop._dbus_signature)
660
e.setAttribute(u"access", prop._dbus_access)
931
for if_tag in document.getElementsByTagName("interface"):
662
for if_tag in document.getElementsByTagName(u"interface"):
933
663
for tag in (make_tag(document, name, prop)
935
in self._get_all_dbus_things("property")
665
in self._get_all_dbus_properties()
936
666
if prop._dbus_interface
937
== if_tag.getAttribute("name")):
667
== if_tag.getAttribute(u"name")):
938
668
if_tag.appendChild(tag)
939
# Add interface annotation tags
940
for annotation, value in dict(
942
*(annotations().iteritems()
943
for name, annotations in
944
self._get_all_dbus_things("interface")
945
if name == if_tag.getAttribute("name")
947
attr_tag = document.createElement("annotation")
948
attr_tag.setAttribute("name", annotation)
949
attr_tag.setAttribute("value", value)
950
if_tag.appendChild(attr_tag)
951
669
# Add the names to the return values for the
952
670
# "org.freedesktop.DBus.Properties" methods
953
if (if_tag.getAttribute("name")
954
== "org.freedesktop.DBus.Properties"):
955
for cn in if_tag.getElementsByTagName("method"):
956
if cn.getAttribute("name") == "Get":
957
for arg in cn.getElementsByTagName("arg"):
958
if (arg.getAttribute("direction")
960
arg.setAttribute("name", "value")
961
elif cn.getAttribute("name") == "GetAll":
962
for arg in cn.getElementsByTagName("arg"):
963
if (arg.getAttribute("direction")
965
arg.setAttribute("name", "props")
966
xmlstring = document.toxml("utf-8")
671
if (if_tag.getAttribute(u"name")
672
== u"org.freedesktop.DBus.Properties"):
673
for cn in if_tag.getElementsByTagName(u"method"):
674
if cn.getAttribute(u"name") == u"Get":
675
for arg in cn.getElementsByTagName(u"arg"):
676
if (arg.getAttribute(u"direction")
678
arg.setAttribute(u"name", u"value")
679
elif cn.getAttribute(u"name") == u"GetAll":
680
for arg in cn.getElementsByTagName(u"arg"):
681
if (arg.getAttribute(u"direction")
683
arg.setAttribute(u"name", u"props")
684
xmlstring = document.toxml(u"utf-8")
967
685
document.unlink()
968
686
except (AttributeError, xml.dom.DOMException,
969
xml.parsers.expat.ExpatError) as error:
970
logger.error("Failed to override Introspection method",
687
xml.parsers.expat.ExpatError), error:
688
logger.error(u"Failed to override Introspection method",
975
def datetime_to_dbus (dt, variant_level=0):
976
"""Convert a UTC datetime.datetime() to a D-Bus type."""
978
return dbus.String("", variant_level = variant_level)
979
return dbus.String(dt.isoformat(),
980
variant_level=variant_level)
983
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
985
"""Applied to an empty subclass of a D-Bus object, this metaclass
986
will add additional D-Bus attributes matching a certain pattern.
988
def __new__(mcs, name, bases, attr):
989
# Go through all the base classes which could have D-Bus
990
# methods, signals, or properties in them
991
old_interface_names = []
992
for base in (b for b in bases
993
if issubclass(b, dbus.service.Object)):
994
# Go though all attributes of the base class
995
for attrname, attribute in inspect.getmembers(base):
996
# Ignore non-D-Bus attributes, and D-Bus attributes
997
# with the wrong interface name
998
if (not hasattr(attribute, "_dbus_interface")
999
or not attribute._dbus_interface
1000
.startswith("se.recompile.Mandos")):
1002
# Create an alternate D-Bus interface name based on
1004
alt_interface = (attribute._dbus_interface
1005
.replace("se.recompile.Mandos",
1006
"se.bsnet.fukt.Mandos"))
1007
if alt_interface != attribute._dbus_interface:
1008
old_interface_names.append(alt_interface)
1009
# Is this a D-Bus signal?
1010
if getattr(attribute, "_dbus_is_signal", False):
1011
# Extract the original non-method function by
1013
nonmethod_func = (dict(
1014
zip(attribute.func_code.co_freevars,
1015
attribute.__closure__))["func"]
1017
# Create a new, but exactly alike, function
1018
# object, and decorate it to be a new D-Bus signal
1019
# with the alternate D-Bus interface name
1020
new_function = (dbus.service.signal
1022
attribute._dbus_signature)
1023
(types.FunctionType(
1024
nonmethod_func.func_code,
1025
nonmethod_func.func_globals,
1026
nonmethod_func.func_name,
1027
nonmethod_func.func_defaults,
1028
nonmethod_func.func_closure)))
1029
# Define a creator of a function to call both the
1030
# old and new functions, so both the old and new
1031
# signals gets sent when the function is called
1032
def fixscope(func1, func2):
1033
"""This function is a scope container to pass
1034
func1 and func2 to the "call_both" function
1035
outside of its arguments"""
1036
def call_both(*args, **kwargs):
1037
"""This function will emit two D-Bus
1038
signals by calling func1 and func2"""
1039
func1(*args, **kwargs)
1040
func2(*args, **kwargs)
1042
# Create the "call_both" function and add it to
1044
attr[attrname] = fixscope(attribute,
1046
# Is this a D-Bus method?
1047
elif getattr(attribute, "_dbus_is_method", False):
1048
# Create a new, but exactly alike, function
1049
# object. Decorate it to be a new D-Bus method
1050
# with the alternate D-Bus interface name. Add it
1052
attr[attrname] = (dbus.service.method
1054
attribute._dbus_in_signature,
1055
attribute._dbus_out_signature)
1057
(attribute.func_code,
1058
attribute.func_globals,
1059
attribute.func_name,
1060
attribute.func_defaults,
1061
attribute.func_closure)))
1062
# Is this a D-Bus property?
1063
elif getattr(attribute, "_dbus_is_property", False):
1064
# Create a new, but exactly alike, function
1065
# object, and decorate it to be a new D-Bus
1066
# property with the alternate D-Bus interface
1067
# name. Add it to the class.
1068
attr[attrname] = (dbus_service_property
1070
attribute._dbus_signature,
1071
attribute._dbus_access,
1073
._dbus_get_args_options
1076
(attribute.func_code,
1077
attribute.func_globals,
1078
attribute.func_name,
1079
attribute.func_defaults,
1080
attribute.func_closure)))
1081
# Is this a D-Bus interface?
1082
elif getattr(attribute, "_dbus_is_interface", False):
1083
# Create a new, but exactly alike, function
1084
# object. Decorate it to be a new D-Bus interface
1085
# with the alternate D-Bus interface name. Add it
1087
attr[attrname] = (dbus_interface_annotations
1090
(attribute.func_code,
1091
attribute.func_globals,
1092
attribute.func_name,
1093
attribute.func_defaults,
1094
attribute.func_closure)))
1095
# Deprecate all old interfaces
1096
basename="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1097
for old_interface_name in old_interface_names:
1098
@dbus_interface_annotations(old_interface_name)
1100
return { "org.freedesktop.DBus.Deprecated": "true" }
1101
# Find an unused name
1102
for aname in (basename.format(i) for i in
1104
if aname not in attr:
1107
return type.__new__(mcs, name, bases, attr)
1110
693
class ClientDBus(Client, DBusObjectWithProperties):
1111
694
"""A Client class using D-Bus
1125
704
Client.__init__(self, *args, **kwargs)
1126
705
# Only now, when this client is initialized, can it show up on
1128
client_object_name = unicode(self.name).translate(
1129
{ord("."): ord("_"),
1130
ord("-"): ord("_")})
1131
707
self.dbus_object_path = (dbus.ObjectPath
1132
("/clients/" + client_object_name))
709
+ self.name.replace(u".", u"_")))
1133
710
DBusObjectWithProperties.__init__(self, self.bus,
1134
711
self.dbus_object_path)
1136
def notifychangeproperty(transform_func,
1137
dbus_name, type_func=lambda x: x,
1139
""" Modify a variable so that it's a property which announces
1140
its changes to DBus.
1142
transform_fun: Function that takes a value and a variant_level
1143
and transforms it to a D-Bus type.
1144
dbus_name: D-Bus name of the variable
1145
type_func: Function that transform the value before sending it
1146
to the D-Bus. Default: no transform
1147
variant_level: D-Bus variant level. Default: 1
1149
attrname = "_{0}".format(dbus_name)
1150
def setter(self, value):
1151
if hasattr(self, "dbus_object_path"):
1152
if (not hasattr(self, attrname) or
1153
type_func(getattr(self, attrname, None))
1154
!= type_func(value)):
1155
dbus_value = transform_func(type_func(value),
1158
self.PropertyChanged(dbus.String(dbus_name),
1160
setattr(self, attrname, value)
1162
return property(lambda self: getattr(self, attrname), setter)
1165
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1166
approvals_pending = notifychangeproperty(dbus.Boolean,
1169
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1170
last_enabled = notifychangeproperty(datetime_to_dbus,
1172
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1173
type_func = lambda checker:
1174
checker is not None)
1175
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1177
last_checker_status = notifychangeproperty(dbus.Int16,
1178
"LastCheckerStatus")
1179
last_approval_request = notifychangeproperty(
1180
datetime_to_dbus, "LastApprovalRequest")
1181
approved_by_default = notifychangeproperty(dbus.Boolean,
1182
"ApprovedByDefault")
1183
approval_delay = notifychangeproperty(dbus.UInt64,
1186
timedelta_to_milliseconds)
1187
approval_duration = notifychangeproperty(
1188
dbus.UInt64, "ApprovalDuration",
1189
type_func = timedelta_to_milliseconds)
1190
host = notifychangeproperty(dbus.String, "Host")
1191
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1193
timedelta_to_milliseconds)
1194
extended_timeout = notifychangeproperty(
1195
dbus.UInt64, "ExtendedTimeout",
1196
type_func = timedelta_to_milliseconds)
1197
interval = notifychangeproperty(dbus.UInt64,
1200
timedelta_to_milliseconds)
1201
checker_command = notifychangeproperty(dbus.String, "Checker")
1203
del notifychangeproperty
714
def _datetime_to_dbus(dt, variant_level=0):
715
"""Convert a UTC datetime.datetime() to a D-Bus type."""
716
return dbus.String(dt.isoformat(),
717
variant_level=variant_level)
720
oldstate = getattr(self, u"enabled", False)
721
r = Client.enable(self)
722
if oldstate != self.enabled:
724
self.PropertyChanged(dbus.String(u"enabled"),
725
dbus.Boolean(True, variant_level=1))
726
self.PropertyChanged(
727
dbus.String(u"last_enabled"),
728
self._datetime_to_dbus(self.last_enabled,
732
def disable(self, quiet = False):
733
oldstate = getattr(self, u"enabled", False)
734
r = Client.disable(self, quiet=quiet)
735
if not quiet and oldstate != self.enabled:
737
self.PropertyChanged(dbus.String(u"enabled"),
738
dbus.Boolean(False, variant_level=1))
1205
741
def __del__(self, *args, **kwargs):
1207
743
self.remove_from_connection()
1208
744
except LookupError:
1210
if hasattr(DBusObjectWithProperties, "__del__"):
746
if hasattr(DBusObjectWithProperties, u"__del__"):
1211
747
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1212
748
Client.__del__(self, *args, **kwargs)
1242
790
and old_checker_pid != self.checker.pid):
1243
791
# Emit D-Bus signal
1244
792
self.CheckerStarted(self.current_checker_command)
793
self.PropertyChanged(
794
dbus.String(u"checker_running"),
795
dbus.Boolean(True, variant_level=1))
798
def stop_checker(self, *args, **kwargs):
799
old_checker = getattr(self, u"checker", None)
800
r = Client.stop_checker(self, *args, **kwargs)
801
if (old_checker is not None
802
and getattr(self, u"checker", None) is None):
803
self.PropertyChanged(dbus.String(u"checker_running"),
804
dbus.Boolean(False, variant_level=1))
1247
807
def _reset_approved(self):
1248
self.approved = None
808
self._approved = None
1251
811
def approve(self, value=True):
1252
self.send_changedstate()
1253
self.approved = value
1254
gobject.timeout_add(timedelta_to_milliseconds
1255
(self.approval_duration),
1256
self._reset_approved)
812
self._approved = value
813
gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
815
def approved_pending(self):
816
return self.approvals_pending > 0
1259
819
## D-Bus methods, signals & properties
1260
_interface = "se.recompile.Mandos.Client"
1264
@dbus_interface_annotations(_interface)
1266
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
820
_interface = u"se.bsnet.fukt.Mandos.Client"
1271
824
# CheckerCompleted - signal
1272
@dbus.service.signal(_interface, signature="nxs")
825
@dbus.service.signal(_interface, signature=u"nxs")
1273
826
def CheckerCompleted(self, exitcode, waitstatus, command):
1277
830
# CheckerStarted - signal
1278
@dbus.service.signal(_interface, signature="s")
831
@dbus.service.signal(_interface, signature=u"s")
1279
832
def CheckerStarted(self, command):
1283
836
# PropertyChanged - signal
1284
@dbus.service.signal(_interface, signature="sv")
837
@dbus.service.signal(_interface, signature=u"sv")
1285
838
def PropertyChanged(self, property, value):
1289
842
# GotSecret - signal
843
# Is sent after succesfull transfer of secret from mandos-server to mandos-client
1290
844
@dbus.service.signal(_interface)
1291
845
def GotSecret(self):
1293
Is sent after a successful transfer of secret from the Mandos
1294
server to mandos-client
1298
849
# Rejected - signal
1299
@dbus.service.signal(_interface, signature="s")
850
@dbus.service.signal(_interface, signature=u"s")
1300
851
def Rejected(self, reason):
1304
855
# NeedApproval - signal
1305
@dbus.service.signal(_interface, signature="tb")
856
@dbus.service.signal(_interface, signature=u"db")
1306
857
def NeedApproval(self, timeout, default):
1308
return self.need_approval()
1312
863
# Approve - method
1313
@dbus.service.method(_interface, in_signature="b")
864
@dbus.service.method(_interface, in_signature=u"b")
1314
865
def Approve(self, value):
1315
866
self.approve(value)
1317
868
# CheckedOK - method
1318
869
@dbus.service.method(_interface)
1319
870
def CheckedOK(self):
871
return self.checked_ok()
1322
873
# Enable - method
1323
874
@dbus.service.method(_interface)
1347
# ApprovalPending - property
1348
@dbus_service_property(_interface, signature="b", access="read")
1349
def ApprovalPending_dbus_property(self):
1350
return dbus.Boolean(bool(self.approvals_pending))
1352
# ApprovedByDefault - property
1353
@dbus_service_property(_interface, signature="b",
1355
def ApprovedByDefault_dbus_property(self, value=None):
1356
if value is None: # get
1357
return dbus.Boolean(self.approved_by_default)
1358
self.approved_by_default = bool(value)
1360
# ApprovalDelay - property
1361
@dbus_service_property(_interface, signature="t",
1363
def ApprovalDelay_dbus_property(self, value=None):
1364
if value is None: # get
1365
return dbus.UInt64(self.approval_delay_milliseconds())
1366
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1368
# ApprovalDuration - property
1369
@dbus_service_property(_interface, signature="t",
1371
def ApprovalDuration_dbus_property(self, value=None):
1372
if value is None: # get
1373
return dbus.UInt64(timedelta_to_milliseconds(
1374
self.approval_duration))
1375
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1378
@dbus_service_property(_interface, signature="s", access="read")
1379
def Name_dbus_property(self):
898
# approved_pending - property
899
@dbus_service_property(_interface, signature=u"b", access=u"read")
900
def approved_pending_dbus_property(self):
901
return dbus.Boolean(self.approved_pending())
903
# approved_by_default - property
904
@dbus_service_property(_interface, signature=u"b",
906
def approved_by_default_dbus_property(self):
907
return dbus.Boolean(self.approved_by_default)
909
# approved_delay - property
910
@dbus_service_property(_interface, signature=u"t",
912
def approved_delay_dbus_property(self):
913
return dbus.UInt64(self.approved_delay_milliseconds())
915
# approved_duration - property
916
@dbus_service_property(_interface, signature=u"t",
918
def approved_duration_dbus_property(self):
919
return dbus.UInt64(self._timedelta_to_milliseconds(
920
self.approved_duration))
923
@dbus_service_property(_interface, signature=u"s", access=u"read")
924
def name_dbus_property(self):
1380
925
return dbus.String(self.name)
1382
# Fingerprint - property
1383
@dbus_service_property(_interface, signature="s", access="read")
1384
def Fingerprint_dbus_property(self):
927
# fingerprint - property
928
@dbus_service_property(_interface, signature=u"s", access=u"read")
929
def fingerprint_dbus_property(self):
1385
930
return dbus.String(self.fingerprint)
1388
@dbus_service_property(_interface, signature="s",
1390
def Host_dbus_property(self, value=None):
933
@dbus_service_property(_interface, signature=u"s",
935
def host_dbus_property(self, value=None):
1391
936
if value is None: # get
1392
937
return dbus.String(self.host)
1393
self.host = unicode(value)
1395
# Created - property
1396
@dbus_service_property(_interface, signature="s", access="read")
1397
def Created_dbus_property(self):
1398
return datetime_to_dbus(self.created)
1400
# LastEnabled - property
1401
@dbus_service_property(_interface, signature="s", access="read")
1402
def LastEnabled_dbus_property(self):
1403
return datetime_to_dbus(self.last_enabled)
1405
# Enabled - property
1406
@dbus_service_property(_interface, signature="b",
1408
def Enabled_dbus_property(self, value=None):
940
self.PropertyChanged(dbus.String(u"host"),
941
dbus.String(value, variant_level=1))
944
@dbus_service_property(_interface, signature=u"s", access=u"read")
945
def created_dbus_property(self):
946
return dbus.String(self._datetime_to_dbus(self.created))
948
# last_enabled - property
949
@dbus_service_property(_interface, signature=u"s", access=u"read")
950
def last_enabled_dbus_property(self):
951
if self.last_enabled is None:
952
return dbus.String(u"")
953
return dbus.String(self._datetime_to_dbus(self.last_enabled))
956
@dbus_service_property(_interface, signature=u"b",
958
def enabled_dbus_property(self, value=None):
1409
959
if value is None: # get
1410
960
return dbus.Boolean(self.enabled)
1416
# LastCheckedOK - property
1417
@dbus_service_property(_interface, signature="s",
1419
def LastCheckedOK_dbus_property(self, value=None):
966
# last_checked_ok - property
967
@dbus_service_property(_interface, signature=u"s",
969
def last_checked_ok_dbus_property(self, value=None):
1420
970
if value is not None:
1421
971
self.checked_ok()
1423
return datetime_to_dbus(self.last_checked_ok)
1425
# LastCheckerStatus - property
1426
@dbus_service_property(_interface, signature="n",
1428
def LastCheckerStatus_dbus_property(self):
1429
return dbus.Int16(self.last_checker_status)
1431
# Expires - property
1432
@dbus_service_property(_interface, signature="s", access="read")
1433
def Expires_dbus_property(self):
1434
return datetime_to_dbus(self.expires)
1436
# LastApprovalRequest - property
1437
@dbus_service_property(_interface, signature="s", access="read")
1438
def LastApprovalRequest_dbus_property(self):
1439
return datetime_to_dbus(self.last_approval_request)
1441
# Timeout - property
1442
@dbus_service_property(_interface, signature="t",
1444
def Timeout_dbus_property(self, value=None):
973
if self.last_checked_ok is None:
974
return dbus.String(u"")
975
return dbus.String(self._datetime_to_dbus(self
979
@dbus_service_property(_interface, signature=u"t",
981
def timeout_dbus_property(self, value=None):
1445
982
if value is None: # get
1446
983
return dbus.UInt64(self.timeout_milliseconds())
1447
984
self.timeout = datetime.timedelta(0, 0, 0, value)
986
self.PropertyChanged(dbus.String(u"timeout"),
987
dbus.UInt64(value, variant_level=1))
988
if getattr(self, u"disable_initiator_tag", None) is None:
1448
990
# Reschedule timeout
1450
now = datetime.datetime.utcnow()
1451
time_to_die = timedelta_to_milliseconds(
1452
(self.last_checked_ok + self.timeout) - now)
1453
if time_to_die <= 0:
1454
# The timeout has passed
1457
self.expires = (now +
1458
datetime.timedelta(milliseconds =
1460
if (getattr(self, "disable_initiator_tag", None)
1463
gobject.source_remove(self.disable_initiator_tag)
1464
self.disable_initiator_tag = (gobject.timeout_add
1468
# ExtendedTimeout - property
1469
@dbus_service_property(_interface, signature="t",
1471
def ExtendedTimeout_dbus_property(self, value=None):
1472
if value is None: # get
1473
return dbus.UInt64(self.extended_timeout_milliseconds())
1474
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1476
# Interval - property
1477
@dbus_service_property(_interface, signature="t",
1479
def Interval_dbus_property(self, value=None):
991
gobject.source_remove(self.disable_initiator_tag)
992
self.disable_initiator_tag = None
994
_timedelta_to_milliseconds((self
1000
# The timeout has passed
1003
self.disable_initiator_tag = (gobject.timeout_add
1004
(time_to_die, self.disable))
1006
# interval - property
1007
@dbus_service_property(_interface, signature=u"t",
1008
access=u"readwrite")
1009
def interval_dbus_property(self, value=None):
1480
1010
if value is None: # get
1481
1011
return dbus.UInt64(self.interval_milliseconds())
1482
1012
self.interval = datetime.timedelta(0, 0, 0, value)
1483
if getattr(self, "checker_initiator_tag", None) is None:
1014
self.PropertyChanged(dbus.String(u"interval"),
1015
dbus.UInt64(value, variant_level=1))
1016
if getattr(self, u"checker_initiator_tag", None) is None:
1486
# Reschedule checker run
1487
gobject.source_remove(self.checker_initiator_tag)
1488
self.checker_initiator_tag = (gobject.timeout_add
1489
(value, self.start_checker))
1490
self.start_checker() # Start one now, too
1492
# Checker - property
1493
@dbus_service_property(_interface, signature="s",
1495
def Checker_dbus_property(self, value=None):
1018
# Reschedule checker run
1019
gobject.source_remove(self.checker_initiator_tag)
1020
self.checker_initiator_tag = (gobject.timeout_add
1021
(value, self.start_checker))
1022
self.start_checker() # Start one now, too
1024
# checker - property
1025
@dbus_service_property(_interface, signature=u"s",
1026
access=u"readwrite")
1027
def checker_dbus_property(self, value=None):
1496
1028
if value is None: # get
1497
1029
return dbus.String(self.checker_command)
1498
self.checker_command = unicode(value)
1030
self.checker_command = value
1032
self.PropertyChanged(dbus.String(u"checker"),
1033
dbus.String(self.checker_command,
1500
# CheckerRunning - property
1501
@dbus_service_property(_interface, signature="b",
1503
def CheckerRunning_dbus_property(self, value=None):
1036
# checker_running - property
1037
@dbus_service_property(_interface, signature=u"b",
1038
access=u"readwrite")
1039
def checker_running_dbus_property(self, value=None):
1504
1040
if value is None: # get
1505
1041
return dbus.Boolean(self.checker is not None)
1561
1093
def handle(self):
1562
1094
with contextlib.closing(self.server.child_pipe) as child_pipe:
1563
logger.info("TCP connection from: %s",
1095
logger.info(u"TCP connection from: %s",
1564
1096
unicode(self.client_address))
1565
logger.debug("Pipe FD: %d",
1097
logger.debug(u"Pipe FD: %d",
1566
1098
self.server.child_pipe.fileno())
1568
1100
session = (gnutls.connection
1569
1101
.ClientSession(self.request,
1570
1102
gnutls.connection
1571
1103
.X509Credentials()))
1573
1105
# Note: gnutls.connection.X509Credentials is really a
1574
1106
# generic GnuTLS certificate credentials object so long as
1575
1107
# no X.509 keys are added to it. Therefore, we can use it
1576
1108
# here despite using OpenPGP certificates.
1578
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1579
# "+AES-256-CBC", "+SHA1",
1580
# "+COMP-NULL", "+CTYPE-OPENPGP",
1110
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1111
# u"+AES-256-CBC", u"+SHA1",
1112
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1582
1114
# Use a fallback default, since this MUST be set.
1583
1115
priority = self.server.gnutls_priority
1584
1116
if priority is None:
1117
priority = u"NORMAL"
1586
1118
(gnutls.library.functions
1587
1119
.gnutls_priority_set_direct(session._c_object,
1588
1120
priority, None))
1590
1122
# Start communication using the Mandos protocol
1591
1123
# Get protocol number
1592
1124
line = self.request.makefile().readline()
1593
logger.debug("Protocol version: %r", line)
1125
logger.debug(u"Protocol version: %r", line)
1595
1127
if int(line.strip().split()[0]) > 1:
1596
1128
raise RuntimeError
1597
except (ValueError, IndexError, RuntimeError) as error:
1598
logger.error("Unknown protocol version: %s", error)
1129
except (ValueError, IndexError, RuntimeError), error:
1130
logger.error(u"Unknown protocol version: %s", error)
1601
1133
# Start GnuTLS connection
1603
1135
session.handshake()
1604
except gnutls.errors.GNUTLSError as error:
1605
logger.warning("Handshake failed: %s", error)
1136
except gnutls.errors.GNUTLSError, error:
1137
logger.warning(u"Handshake failed: %s", error)
1606
1138
# Do not run session.bye() here: the session is not
1607
1139
# established. Just abandon the request.
1609
logger.debug("Handshake succeeded")
1141
logger.debug(u"Handshake succeeded")
1611
1143
approval_required = False
1614
1146
fpr = self.fingerprint(self.peer_certificate
1617
gnutls.errors.GNUTLSError) as error:
1618
logger.warning("Bad certificate: %s", error)
1148
except (TypeError, gnutls.errors.GNUTLSError), error:
1149
logger.warning(u"Bad certificate: %s", error)
1620
logger.debug("Fingerprint: %s", fpr)
1151
logger.debug(u"Fingerprint: %s", fpr)
1623
1154
client = ProxyClient(child_pipe, fpr,
1624
1155
self.client_address)
1625
1156
except KeyError:
1628
if client.approval_delay:
1629
delay = client.approval_delay
1159
if client.approved_delay:
1160
delay = client.approved_delay
1630
1161
client.approvals_pending += 1
1631
1162
approval_required = True
1634
1165
if not client.enabled:
1635
logger.info("Client %s is disabled",
1166
logger.warning(u"Client %s is disabled",
1637
1168
if self.server.use_dbus:
1638
1169
# Emit D-Bus signal
1639
client.Rejected("Disabled")
1170
client.Rejected("Disabled")
1642
if client.approved or not client.approval_delay:
1173
if client._approved or not client.approved_delay:
1643
1174
#We are approved or approval is disabled
1645
elif client.approved is None:
1646
logger.info("Client %s needs approval",
1176
elif client._approved is None:
1177
logger.info(u"Client %s need approval",
1648
1179
if self.server.use_dbus:
1649
1180
# Emit D-Bus signal
1650
1181
client.NeedApproval(
1651
client.approval_delay_milliseconds(),
1182
client.approved_delay_milliseconds(),
1652
1183
client.approved_by_default)
1654
logger.warning("Client %s was not approved",
1185
logger.warning(u"Client %s was not approved",
1656
1187
if self.server.use_dbus:
1657
1188
# Emit D-Bus signal
1658
client.Rejected("Denied")
1189
client.Rejected("Disapproved")
1661
1192
#wait until timeout or approved
1193
#x = float(client._timedelta_to_milliseconds(delay))
1662
1194
time = datetime.datetime.now()
1663
1195
client.changedstate.acquire()
1664
(client.changedstate.wait
1665
(float(client.timedelta_to_milliseconds(delay)
1196
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1667
1197
client.changedstate.release()
1668
1198
time2 = datetime.datetime.now()
1669
1199
if (time2 - time) >= delay:
1926
1441
for cond, name in
1927
1442
condition_names.iteritems()
1928
1443
if cond & condition)
1929
# error, or the other end of multiprocessing.Pipe has closed
1930
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1931
# Wait for other process to exit
1444
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1447
# error or the other end of multiprocessing.Pipe has closed
1448
if condition & gobject.IO_HUP or condition & gobject.IO_ERR:
1935
1451
# Read a request from the child
1936
1452
request = parent_pipe.recv()
1453
logger.debug(u"IPC request: %s", repr(request))
1937
1454
command = request[0]
1939
1456
if command == 'init':
1940
1457
fpr = request[1]
1941
1458
address = request[2]
1943
for c in self.clients.itervalues():
1460
for c in self.clients:
1944
1461
if c.fingerprint == fpr:
1948
logger.info("Client not found for fingerprint: %s, ad"
1949
"dress: %s", fpr, address)
1465
logger.warning(u"Client not found for fingerprint: %s, ad"
1466
u"dress: %s", fpr, address)
1950
1467
if self.use_dbus:
1951
1468
# Emit D-Bus signal
1952
mandos_dbus_service.ClientNotFound(fpr,
1469
mandos_dbus_service.ClientNotFound(fpr, address)
1954
1470
parent_pipe.send(False)
1957
1473
gobject.io_add_watch(parent_pipe.fileno(),
1958
1474
gobject.IO_IN | gobject.IO_HUP,
1959
1475
functools.partial(self.handle_ipc,
1476
parent_pipe = parent_pipe,
1477
client_object = client))
1965
1478
parent_pipe.send(True)
1966
# remove the old hook in favor of the new above hook on
1479
# remove the old hook in favor of the new above hook on same fileno
1969
1481
if command == 'funcall':
1970
1482
funcname = request[1]
1971
1483
args = request[2]
1972
1484
kwargs = request[3]
1974
parent_pipe.send(('data', getattr(client_object,
1486
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1978
1488
if command == 'getattr':
1979
1489
attrname = request[1]
1980
1490
if callable(client_object.__getattribute__(attrname)):
1981
1491
parent_pipe.send(('function',))
1983
parent_pipe.send(('data', client_object
1984
.__getattribute__(attrname)))
1493
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1986
1495
if command == 'setattr':
1987
1496
attrname = request[1]
1988
1497
value = request[2]
1989
1498
setattr(client_object, attrname, value)
1994
1503
def string_to_delta(interval):
1995
1504
"""Parse a string and return a datetime.timedelta
1997
>>> string_to_delta('7d')
1506
>>> string_to_delta(u'7d')
1998
1507
datetime.timedelta(7)
1999
>>> string_to_delta('60s')
1508
>>> string_to_delta(u'60s')
2000
1509
datetime.timedelta(0, 60)
2001
>>> string_to_delta('60m')
1510
>>> string_to_delta(u'60m')
2002
1511
datetime.timedelta(0, 3600)
2003
>>> string_to_delta('24h')
1512
>>> string_to_delta(u'24h')
2004
1513
datetime.timedelta(1)
2005
>>> string_to_delta('1w')
1514
>>> string_to_delta(u'1w')
2006
1515
datetime.timedelta(7)
2007
>>> string_to_delta('5m 30s')
1516
>>> string_to_delta(u'5m 30s')
2008
1517
datetime.timedelta(0, 330)
2010
1519
timevalue = datetime.timedelta(0)
2060
1593
##################################################################
2061
1594
# Parsing of options, both command line and config file
2063
parser = argparse.ArgumentParser()
2064
parser.add_argument("-v", "--version", action="version",
2065
version = "%%(prog)s %s" % version,
2066
help="show version number and exit")
2067
parser.add_argument("-i", "--interface", metavar="IF",
2068
help="Bind to interface IF")
2069
parser.add_argument("-a", "--address",
2070
help="Address to listen for requests on")
2071
parser.add_argument("-p", "--port", type=int,
2072
help="Port number to receive requests on")
2073
parser.add_argument("--check", action="store_true",
2074
help="Run self-test")
2075
parser.add_argument("--debug", action="store_true",
2076
help="Debug mode; run in foreground and log"
2078
parser.add_argument("--debuglevel", metavar="LEVEL",
2079
help="Debug level for stdout output")
2080
parser.add_argument("--priority", help="GnuTLS"
2081
" priority string (see GnuTLS documentation)")
2082
parser.add_argument("--servicename",
2083
metavar="NAME", help="Zeroconf service name")
2084
parser.add_argument("--configdir",
2085
default="/etc/mandos", metavar="DIR",
2086
help="Directory to search for configuration"
2088
parser.add_argument("--no-dbus", action="store_false",
2089
dest="use_dbus", help="Do not provide D-Bus"
2090
" system bus interface")
2091
parser.add_argument("--no-ipv6", action="store_false",
2092
dest="use_ipv6", help="Do not use IPv6")
2093
parser.add_argument("--no-restore", action="store_false",
2094
dest="restore", help="Do not restore stored"
2096
parser.add_argument("--statedir", metavar="DIR",
2097
help="Directory to save/restore state in")
2099
options = parser.parse_args()
1596
parser = optparse.OptionParser(version = "%%prog %s" % version)
1597
parser.add_option("-i", u"--interface", type=u"string",
1598
metavar="IF", help=u"Bind to interface IF")
1599
parser.add_option("-a", u"--address", type=u"string",
1600
help=u"Address to listen for requests on")
1601
parser.add_option("-p", u"--port", type=u"int",
1602
help=u"Port number to receive requests on")
1603
parser.add_option("--check", action=u"store_true",
1604
help=u"Run self-test")
1605
parser.add_option("--debug", action=u"store_true",
1606
help=u"Debug mode; run in foreground and log to"
1608
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1609
u" priority string (see GnuTLS documentation)")
1610
parser.add_option("--servicename", type=u"string",
1611
metavar=u"NAME", help=u"Zeroconf service name")
1612
parser.add_option("--configdir", type=u"string",
1613
default=u"/etc/mandos", metavar=u"DIR",
1614
help=u"Directory to search for configuration"
1616
parser.add_option("--no-dbus", action=u"store_false",
1617
dest=u"use_dbus", help=u"Do not provide D-Bus"
1618
u" system bus interface")
1619
parser.add_option("--no-ipv6", action=u"store_false",
1620
dest=u"use_ipv6", help=u"Do not use IPv6")
1621
options = parser.parse_args()[0]
2101
1623
if options.check:
2153
1671
##################################################################
2155
1673
# For convenience
2156
debug = server_settings["debug"]
2157
debuglevel = server_settings["debuglevel"]
2158
use_dbus = server_settings["use_dbus"]
2159
use_ipv6 = server_settings["use_ipv6"]
2160
stored_state_path = os.path.join(server_settings["statedir"],
2164
initlogger(debug, logging.DEBUG)
2169
level = getattr(logging, debuglevel.upper())
2170
initlogger(debug, level)
2172
if server_settings["servicename"] != "Mandos":
1674
debug = server_settings[u"debug"]
1675
use_dbus = server_settings[u"use_dbus"]
1676
use_ipv6 = server_settings[u"use_ipv6"]
1679
syslogger.setLevel(logging.WARNING)
1680
console.setLevel(logging.WARNING)
1682
if server_settings[u"servicename"] != u"Mandos":
2173
1683
syslogger.setFormatter(logging.Formatter
2174
('Mandos (%s) [%%(process)d]:'
2175
' %%(levelname)s: %%(message)s'
2176
% server_settings["servicename"]))
1684
(u'Mandos (%s) [%%(process)d]:'
1685
u' %%(levelname)s: %%(message)s'
1686
% server_settings[u"servicename"]))
2178
1688
# Parse config file with clients
2179
client_config = configparser.SafeConfigParser(Client
2181
client_config.read(os.path.join(server_settings["configdir"],
1689
client_defaults = { u"timeout": u"1h",
1691
u"checker": u"fping -q -- %%(host)s",
1693
u"approved_delay": u"5m",
1694
u"approved_duration": u"1s",
1696
client_config = configparser.SafeConfigParser(client_defaults)
1697
client_config.read(os.path.join(server_settings[u"configdir"],
2184
1700
global mandos_dbus_service
2185
1701
mandos_dbus_service = None
2187
tcp_server = MandosServer((server_settings["address"],
2188
server_settings["port"]),
1703
tcp_server = MandosServer((server_settings[u"address"],
1704
server_settings[u"port"]),
2190
interface=(server_settings["interface"]
1706
interface=server_settings[u"interface"],
2192
1707
use_ipv6=use_ipv6,
2193
1708
gnutls_priority=
2194
server_settings["priority"],
1709
server_settings[u"priority"],
2195
1710
use_dbus=use_dbus)
2197
pidfilename = "/var/run/mandos.pid"
2199
pidfile = open(pidfilename, "w")
2201
logger.error("Could not open file %r", pidfilename)
1711
pidfilename = u"/var/run/mandos.pid"
1713
pidfile = open(pidfilename, u"w")
1715
logger.error(u"Could not open file %r", pidfilename)
2204
uid = pwd.getpwnam("_mandos").pw_uid
2205
gid = pwd.getpwnam("_mandos").pw_gid
1718
uid = pwd.getpwnam(u"_mandos").pw_uid
1719
gid = pwd.getpwnam(u"_mandos").pw_gid
2206
1720
except KeyError:
2208
uid = pwd.getpwnam("mandos").pw_uid
2209
gid = pwd.getpwnam("mandos").pw_gid
1722
uid = pwd.getpwnam(u"mandos").pw_uid
1723
gid = pwd.getpwnam(u"mandos").pw_gid
2210
1724
except KeyError:
2212
uid = pwd.getpwnam("nobody").pw_uid
2213
gid = pwd.getpwnam("nobody").pw_gid
1726
uid = pwd.getpwnam(u"nobody").pw_uid
1727
gid = pwd.getpwnam(u"nobody").pw_gid
2214
1728
except KeyError:
2220
except OSError as error:
1734
except OSError, error:
2221
1735
if error[0] != errno.EPERM:
1738
# Enable all possible GnuTLS debugging
2225
# Enable all possible GnuTLS debugging
2227
1740
# "Use a log level over 10 to enable all debugging options."
2228
1741
# - GnuTLS manual
2229
1742
gnutls.library.functions.gnutls_global_set_log_level(11)
2231
1744
@gnutls.library.types.gnutls_log_func
2232
1745
def debug_gnutls(level, string):
2233
logger.debug("GnuTLS: %s", string[:-1])
1746
logger.debug(u"GnuTLS: %s", string[:-1])
2235
1748
(gnutls.library.functions
2236
1749
.gnutls_global_set_log_function(debug_gnutls))
2238
# Redirect stdin so all checkers get /dev/null
2239
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2240
os.dup2(null, sys.stdin.fileno())
2244
# Need to fork before connecting to D-Bus
2246
# Close all input and output, do double fork, etc.
2249
gobject.threads_init()
2251
1751
global main_loop
2252
1752
# From the Avahi example code
2253
DBusGMainLoop(set_as_default=True)
1753
DBusGMainLoop(set_as_default=True )
2254
1754
main_loop = gobject.MainLoop()
2255
1755
bus = dbus.SystemBus()
2256
1756
# End of Avahi example code
2259
bus_name = dbus.service.BusName("se.recompile.Mandos",
1759
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2260
1760
bus, do_not_queue=True)
2261
old_bus_name = (dbus.service.BusName
2262
("se.bsnet.fukt.Mandos", bus,
2264
except dbus.exceptions.NameExistsException as e:
2265
logger.error(unicode(e) + ", disabling D-Bus")
1761
except dbus.exceptions.NameExistsException, e:
1762
logger.error(unicode(e) + u", disabling D-Bus")
2266
1763
use_dbus = False
2267
server_settings["use_dbus"] = False
1764
server_settings[u"use_dbus"] = False
2268
1765
tcp_server.use_dbus = False
2269
1766
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2270
service = AvahiServiceToSyslog(name =
2271
server_settings["servicename"],
2272
servicetype = "_mandos._tcp",
2273
protocol = protocol, bus = bus)
1767
service = AvahiService(name = server_settings[u"servicename"],
1768
servicetype = u"_mandos._tcp",
1769
protocol = protocol, bus = bus)
2274
1770
if server_settings["interface"]:
2275
1771
service.interface = (if_nametoindex
2276
(str(server_settings["interface"])))
1772
(str(server_settings[u"interface"])))
2278
1774
global multiprocessing_manager
2279
1775
multiprocessing_manager = multiprocessing.Manager()
2281
1777
client_class = Client
2283
client_class = functools.partial(ClientDBusTransitional,
2286
client_settings = Client.config_parser(client_config)
2287
old_client_settings = {}
2290
# Get client data and settings from last running state.
2291
if server_settings["restore"]:
2293
with open(stored_state_path, "rb") as stored_state:
2294
clients_data, old_client_settings = (pickle.load
2296
os.remove(stored_state_path)
2297
except IOError as e:
2298
logger.warning("Could not load persistent state: {0}"
2300
if e.errno != errno.ENOENT:
2302
except EOFError as e:
2303
logger.warning("Could not load persistent state: "
2304
"EOFError: {0}".format(e))
2306
with PGPEngine() as pgp:
2307
for client_name, client in clients_data.iteritems():
2308
# Decide which value to use after restoring saved state.
2309
# We have three different values: Old config file,
2310
# new config file, and saved state.
2311
# New config value takes precedence if it differs from old
2312
# config value, otherwise use saved state.
2313
for name, value in client_settings[client_name].items():
2315
# For each value in new config, check if it
2316
# differs from the old config value (Except for
2317
# the "secret" attribute)
2318
if (name != "secret" and
2319
value != old_client_settings[client_name]
2321
client[name] = value
2325
# Clients who has passed its expire date can still be
2326
# enabled if its last checker was successful. Clients
2327
# whose checker succeeded before we stored its state is
2328
# assumed to have successfully run all checkers during
2330
if client["enabled"]:
2331
if datetime.datetime.utcnow() >= client["expires"]:
2332
if not client["last_checked_ok"]:
2334
"disabling client {0} - Client never "
2335
"performed a successful checker"
2336
.format(client_name))
2337
client["enabled"] = False
2338
elif client["last_checker_status"] != 0:
2340
"disabling client {0} - Client "
2341
"last checker failed with error code {1}"
2342
.format(client_name,
2343
client["last_checker_status"]))
2344
client["enabled"] = False
2346
client["expires"] = (datetime.datetime
2348
+ client["timeout"])
2349
logger.debug("Last checker succeeded,"
2350
" keeping {0} enabled"
2351
.format(client_name))
1779
client_class = functools.partial(ClientDBus, bus = bus)
1780
def client_config_items(config, section):
1781
special_settings = {
1782
"approved_by_default":
1783
lambda: config.getboolean(section,
1784
"approved_by_default"),
1786
for name, value in config.items(section):
2353
client["secret"] = (
2354
pgp.decrypt(client["encrypted_secret"],
2355
client_settings[client_name]
2358
# If decryption fails, we use secret from new settings
2359
logger.debug("Failed to decrypt {0} old secret"
2360
.format(client_name))
2361
client["secret"] = (
2362
client_settings[client_name]["secret"])
2365
# Add/remove clients based on new changes made to config
2366
for client_name in (set(old_client_settings)
2367
- set(client_settings)):
2368
del clients_data[client_name]
2369
for client_name in (set(client_settings)
2370
- set(old_client_settings)):
2371
clients_data[client_name] = client_settings[client_name]
2373
# Create all client objects
2374
for client_name, client in clients_data.iteritems():
2375
tcp_server.clients[client_name] = client_class(
2376
name = client_name, settings = client)
1788
yield (name, special_settings[name]())
1792
tcp_server.clients.update(set(
1793
client_class(name = section,
1794
config= dict(client_config_items(
1795
client_config, section)))
1796
for section in client_config.sections()))
2378
1797
if not tcp_server.clients:
2379
logger.warning("No clients defined")
1798
logger.warning(u"No clients defined")
1801
# Redirect stdin so all checkers get /dev/null
1802
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1803
os.dup2(null, sys.stdin.fileno())
1807
# No console logging
1808
logger.removeHandler(console)
1809
# Close all input and output, do double fork, etc.
1815
pidfile.write(str(pid) + "\n")
1818
logger.error(u"Could not write to file %r with PID %d",
1821
# "pidfile" was never created
2385
pidfile.write(str(pid) + "\n".encode("utf-8"))
2388
logger.error("Could not write to file %r with PID %d",
2391
# "pidfile" was never created
2394
1826
signal.signal(signal.SIGINT, signal.SIG_IGN)
2396
1827
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2397
1828
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2400
class MandosDBusService(DBusObjectWithProperties):
1831
class MandosDBusService(dbus.service.Object):
2401
1832
"""A D-Bus proxy object"""
2402
1833
def __init__(self):
2403
dbus.service.Object.__init__(self, bus, "/")
2404
_interface = "se.recompile.Mandos"
2406
@dbus_interface_annotations(_interface)
2408
return { "org.freedesktop.DBus.Property"
2409
".EmitsChangedSignal":
2412
@dbus.service.signal(_interface, signature="o")
1834
dbus.service.Object.__init__(self, bus, u"/")
1835
_interface = u"se.bsnet.fukt.Mandos"
1837
@dbus.service.signal(_interface, signature=u"o")
2413
1838
def ClientAdded(self, objpath):
2417
@dbus.service.signal(_interface, signature="ss")
1842
@dbus.service.signal(_interface, signature=u"ss")
2418
1843
def ClientNotFound(self, fingerprint, address):
2422
@dbus.service.signal(_interface, signature="os")
1847
@dbus.service.signal(_interface, signature=u"os")
2423
1848
def ClientRemoved(self, objpath, name):
2427
@dbus.service.method(_interface, out_signature="ao")
1852
@dbus.service.method(_interface, out_signature=u"ao")
2428
1853
def GetAllClients(self):
2430
1855
return dbus.Array(c.dbus_object_path
2432
tcp_server.clients.itervalues())
1856
for c in tcp_server.clients)
2434
1858
@dbus.service.method(_interface,
2435
out_signature="a{oa{sv}}")
1859
out_signature=u"a{oa{sv}}")
2436
1860
def GetAllClientsWithProperties(self):
2438
1862
return dbus.Dictionary(
2439
((c.dbus_object_path, c.GetAll(""))
2440
for c in tcp_server.clients.itervalues()),
1863
((c.dbus_object_path, c.GetAll(u""))
1864
for c in tcp_server.clients),
1865
signature=u"oa{sv}")
2443
@dbus.service.method(_interface, in_signature="o")
1867
@dbus.service.method(_interface, in_signature=u"o")
2444
1868
def RemoveClient(self, object_path):
2446
for c in tcp_server.clients.itervalues():
1870
for c in tcp_server.clients:
2447
1871
if c.dbus_object_path == object_path:
2448
del tcp_server.clients[c.name]
1872
tcp_server.clients.remove(c)
2449
1873
c.remove_from_connection()
2450
1874
# Don't signal anything except ClientRemoved
2451
1875
c.disable(quiet=True)