88
78
except ImportError:
89
79
SO_BINDTODEVICE = None
92
stored_state_file = "clients.pickle"
94
logger = logging.getLogger()
84
logger = logging.Logger(u'mandos')
95
85
syslogger = (logging.handlers.SysLogHandler
96
86
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
97
address = str("/dev/log")))
100
if_nametoindex = (ctypes.cdll.LoadLibrary
101
(ctypes.util.find_library("c"))
103
except (OSError, AttributeError):
104
def if_nametoindex(interface):
105
"Get an interface index the hard way, i.e. using fcntl()"
106
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
107
with contextlib.closing(socket.socket()) as s:
108
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
109
struct.pack(str("16s16x"),
111
interface_index = struct.unpack(str("I"),
113
return interface_index
116
def initlogger(debug, level=logging.WARNING):
117
"""init logger and add loglevel"""
119
syslogger.setFormatter(logging.Formatter
120
('Mandos [%(process)d]: %(levelname)s:'
122
logger.addHandler(syslogger)
125
console = logging.StreamHandler()
126
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
130
logger.addHandler(console)
131
logger.setLevel(level)
134
class PGPError(Exception):
135
"""Exception if encryption/decryption fails"""
139
class PGPEngine(object):
140
"""A simple class for OpenPGP symmetric encryption & decryption"""
142
self.gnupg = GnuPGInterface.GnuPG()
143
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
144
self.gnupg = GnuPGInterface.GnuPG()
145
self.gnupg.options.meta_interactive = False
146
self.gnupg.options.homedir = self.tempdir
147
self.gnupg.options.extra_args.extend(['--force-mdc',
154
def __exit__ (self, exc_type, exc_value, traceback):
162
if self.tempdir is not None:
163
# Delete contents of tempdir
164
for root, dirs, files in os.walk(self.tempdir,
166
for filename in files:
167
os.remove(os.path.join(root, filename))
169
os.rmdir(os.path.join(root, dirname))
171
os.rmdir(self.tempdir)
174
def password_encode(self, password):
175
# Passphrase can not be empty and can not contain newlines or
176
# NUL bytes. So we prefix it and hex encode it.
177
return b"mandos" + binascii.hexlify(password)
179
def encrypt(self, data, password):
180
self.gnupg.passphrase = self.password_encode(password)
181
with open(os.devnull, "w") as devnull:
183
proc = self.gnupg.run(['--symmetric'],
184
create_fhs=['stdin', 'stdout'],
185
attach_fhs={'stderr': devnull})
186
with contextlib.closing(proc.handles['stdin']) as f:
188
with contextlib.closing(proc.handles['stdout']) as f:
189
ciphertext = f.read()
193
self.gnupg.passphrase = None
196
def decrypt(self, data, password):
197
self.gnupg.passphrase = self.password_encode(password)
198
with open(os.devnull, "w") as devnull:
200
proc = self.gnupg.run(['--decrypt'],
201
create_fhs=['stdin', 'stdout'],
202
attach_fhs={'stderr': devnull})
203
with contextlib.closing(proc.handles['stdin']) as f:
205
with contextlib.closing(proc.handles['stdout']) as f:
206
decrypted_plaintext = f.read()
210
self.gnupg.passphrase = None
211
return decrypted_plaintext
87
address = "/dev/log"))
88
syslogger.setFormatter(logging.Formatter
89
(u'Mandos [%(process)d]: %(levelname)s:'
91
logger.addHandler(syslogger)
93
console = logging.StreamHandler()
94
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
97
logger.addHandler(console)
214
99
class AvahiError(Exception):
215
100
def __init__(self, value, *args, **kwargs):
315
189
dbus.UInt16(self.port),
316
190
avahi.string_array_to_txt_array(self.TXT))
317
191
self.group.Commit()
319
192
def entry_group_state_changed(self, state, error):
320
193
"""Derived from the Avahi example code"""
321
logger.debug("Avahi entry group state change: %i", state)
194
logger.debug(u"Avahi state change: %i", state)
323
196
if state == avahi.ENTRY_GROUP_ESTABLISHED:
324
logger.debug("Zeroconf service established.")
197
logger.debug(u"Zeroconf service established.")
325
198
elif state == avahi.ENTRY_GROUP_COLLISION:
326
logger.info("Zeroconf service name collision.")
199
logger.warning(u"Zeroconf service name collision.")
328
201
elif state == avahi.ENTRY_GROUP_FAILURE:
329
logger.critical("Avahi: Error in group state changed %s",
202
logger.critical(u"Avahi: Error in group state changed %s",
331
raise AvahiGroupError("State changed: {0!s}"
204
raise AvahiGroupError(u"State changed: %s"
334
206
def cleanup(self):
335
207
"""Derived from the Avahi example code"""
336
208
if self.group is not None:
339
except (dbus.exceptions.UnknownMethodException,
340
dbus.exceptions.DBusException):
342
210
self.group = None
345
def server_state_changed(self, state, error=None):
211
def server_state_changed(self, state):
346
212
"""Derived from the Avahi example code"""
347
logger.debug("Avahi server state change: %i", state)
348
bad_states = { avahi.SERVER_INVALID:
349
"Zeroconf server invalid",
350
avahi.SERVER_REGISTERING: None,
351
avahi.SERVER_COLLISION:
352
"Zeroconf server name collision",
353
avahi.SERVER_FAILURE:
354
"Zeroconf server failure" }
355
if state in bad_states:
356
if bad_states[state] is not None:
358
logger.error(bad_states[state])
360
logger.error(bad_states[state] + ": %r", error)
213
if state == avahi.SERVER_COLLISION:
214
logger.error(u"Zeroconf server name collision")
362
216
elif state == avahi.SERVER_RUNNING:
366
logger.debug("Unknown state: %r", state)
368
logger.debug("Unknown state: %r: %r", state, error)
370
218
def activate(self):
371
219
"""Derived from the Avahi example code"""
372
220
if self.server is None:
373
221
self.server = dbus.Interface(
374
222
self.bus.get_object(avahi.DBUS_NAME,
375
avahi.DBUS_PATH_SERVER,
376
follow_name_owner_changes=True),
223
avahi.DBUS_PATH_SERVER),
377
224
avahi.DBUS_INTERFACE_SERVER)
378
self.server.connect_to_signal("StateChanged",
225
self.server.connect_to_signal(u"StateChanged",
379
226
self.server_state_changed)
380
227
self.server_state_changed(self.server.GetState())
383
class AvahiServiceToSyslog(AvahiService):
385
"""Add the new name to the syslog messages"""
386
ret = AvahiService.rename(self)
387
syslogger.setFormatter(logging.Formatter
388
('Mandos ({0}) [%(process)d]:'
389
' %(levelname)s: %(message)s'
394
def timedelta_to_milliseconds(td):
395
"Convert a datetime.timedelta() to milliseconds"
396
return ((td.days * 24 * 60 * 60 * 1000)
397
+ (td.seconds * 1000)
398
+ (td.microseconds // 1000))
401
230
class Client(object):
402
231
"""A representation of a client host served by this server.
405
approved: bool(); 'None' if not yet approved/disapproved
406
approval_delay: datetime.timedelta(); Time to wait for approval
407
approval_duration: datetime.timedelta(); Duration of one approval
234
name: string; from the config file, used in log messages and
236
fingerprint: string (40 or 32 hexadecimal digits); used to
237
uniquely identify the client
238
secret: bytestring; sent verbatim (over TLS) to client
239
host: string; available for use by the checker command
240
created: datetime.datetime(); (UTC) object creation
241
last_enabled: datetime.datetime(); (UTC)
243
last_checked_ok: datetime.datetime(); (UTC) or None
244
timeout: datetime.timedelta(); How long from last_checked_ok
245
until this client is invalid
246
interval: datetime.timedelta(); How often to start a new checker
247
disable_hook: If set, called by disable() as disable_hook(self)
408
248
checker: subprocess.Popen(); a running checker process used
409
249
to see if the client lives.
410
250
'None' if no process is running.
411
checker_callback_tag: a gobject event source tag, or None
412
checker_command: string; External command which is run to check
413
if client lives. %() expansions are done at
251
checker_initiator_tag: a gobject event source tag, or None
252
disable_initiator_tag: - '' -
253
checker_callback_tag: - '' -
254
checker_command: string; External command which is run to check if
255
client lives. %() expansions are done at
414
256
runtime with vars(self) as dict, so that for
415
257
instance %(name)s can be used in the command.
416
checker_initiator_tag: a gobject event source tag, or None
417
created: datetime.datetime(); (UTC) object creation
418
client_structure: Object describing what attributes a client has
419
and is used for storing the client at exit
420
258
current_checker_command: string; current running checker_command
421
disable_initiator_tag: a gobject event source tag, or None
423
fingerprint: string (40 or 32 hexadecimal digits); used to
424
uniquely identify the client
425
host: string; available for use by the checker command
426
interval: datetime.timedelta(); How often to start a new checker
427
last_approval_request: datetime.datetime(); (UTC) or None
428
last_checked_ok: datetime.datetime(); (UTC) or None
429
last_checker_status: integer between 0 and 255 reflecting exit
430
status of last checker. -1 reflects crashed
431
checker, -2 means no checker completed yet.
432
last_enabled: datetime.datetime(); (UTC) or None
433
name: string; from the config file, used in log messages and
435
secret: bytestring; sent verbatim (over TLS) to client
436
timeout: datetime.timedelta(); How long from last_checked_ok
437
until this client is disabled
438
extended_timeout: extra long timeout when secret has been sent
439
runtime_expansions: Allowed attributes for runtime expansion.
440
expires: datetime.datetime(); time (UTC) when a client will be
444
runtime_expansions = ("approval_delay", "approval_duration",
445
"created", "enabled", "fingerprint",
446
"host", "interval", "last_checked_ok",
447
"last_enabled", "name", "timeout")
448
client_defaults = { "timeout": "5m",
449
"extended_timeout": "15m",
451
"checker": "fping -q -- %%(host)s",
453
"approval_delay": "0s",
454
"approval_duration": "1s",
455
"approved_by_default": "True",
262
def _timedelta_to_milliseconds(td):
263
"Convert a datetime.timedelta() to milliseconds"
264
return ((td.days * 24 * 60 * 60 * 1000)
265
+ (td.seconds * 1000)
266
+ (td.microseconds // 1000))
459
268
def timeout_milliseconds(self):
460
269
"Return the 'timeout' attribute in milliseconds"
461
return timedelta_to_milliseconds(self.timeout)
463
def extended_timeout_milliseconds(self):
464
"Return the 'extended_timeout' attribute in milliseconds"
465
return timedelta_to_milliseconds(self.extended_timeout)
270
return self._timedelta_to_milliseconds(self.timeout)
467
272
def interval_milliseconds(self):
468
273
"Return the 'interval' attribute in milliseconds"
469
return timedelta_to_milliseconds(self.interval)
471
def approval_delay_milliseconds(self):
472
return timedelta_to_milliseconds(self.approval_delay)
475
def config_parser(config):
476
"""Construct a new dict of client settings of this form:
477
{ client_name: {setting_name: value, ...}, ...}
478
with exceptions for any special settings as defined above.
479
NOTE: Must be a pure function. Must return the same result
480
value given the same arguments.
483
for client_name in config.sections():
484
section = dict(config.items(client_name))
485
client = settings[client_name] = {}
487
client["host"] = section["host"]
488
# Reformat values from string types to Python types
489
client["approved_by_default"] = config.getboolean(
490
client_name, "approved_by_default")
491
client["enabled"] = config.getboolean(client_name,
494
client["fingerprint"] = (section["fingerprint"].upper()
496
if "secret" in section:
497
client["secret"] = section["secret"].decode("base64")
498
elif "secfile" in section:
499
with open(os.path.expanduser(os.path.expandvars
500
(section["secfile"])),
502
client["secret"] = secfile.read()
504
raise TypeError("No secret or secfile for section {0}"
506
client["timeout"] = string_to_delta(section["timeout"])
507
client["extended_timeout"] = string_to_delta(
508
section["extended_timeout"])
509
client["interval"] = string_to_delta(section["interval"])
510
client["approval_delay"] = string_to_delta(
511
section["approval_delay"])
512
client["approval_duration"] = string_to_delta(
513
section["approval_duration"])
514
client["checker_command"] = section["checker"]
515
client["last_approval_request"] = None
516
client["last_checked_ok"] = None
517
client["last_checker_status"] = -2
521
def __init__(self, settings, name = None):
274
return self._timedelta_to_milliseconds(self.interval)
276
def __init__(self, name = None, disable_hook=None, config=None):
277
"""Note: the 'checker' key in 'config' sets the
278
'checker_command' attribute and *not* the 'checker'
523
# adding all client settings
524
for setting, value in settings.iteritems():
525
setattr(self, setting, value)
528
if not hasattr(self, "last_enabled"):
529
self.last_enabled = datetime.datetime.utcnow()
530
if not hasattr(self, "expires"):
531
self.expires = (datetime.datetime.utcnow()
534
self.last_enabled = None
537
logger.debug("Creating client %r", self.name)
283
logger.debug(u"Creating client %r", self.name)
538
284
# Uppercase and remove spaces from fingerprint for later
539
285
# comparison purposes with return value from the fingerprint()
541
logger.debug(" Fingerprint: %s", self.fingerprint)
542
self.created = settings.get("created",
543
datetime.datetime.utcnow())
545
# attributes specific for this server instance
287
self.fingerprint = (config[u"fingerprint"].upper()
289
logger.debug(u" Fingerprint: %s", self.fingerprint)
290
if u"secret" in config:
291
self.secret = config[u"secret"].decode(u"base64")
292
elif u"secfile" in config:
293
with closing(open(os.path.expanduser
295
(config[u"secfile"])),
297
self.secret = secfile.read()
299
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
302
self.created = datetime.datetime.utcnow()
304
self.last_enabled = None
305
self.last_checked_ok = None
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
308
self.disable_hook = disable_hook
546
309
self.checker = None
547
310
self.checker_initiator_tag = None
548
311
self.disable_initiator_tag = None
549
312
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
550
314
self.current_checker_command = None
552
self.approvals_pending = 0
553
self.changedstate = (multiprocessing_manager
554
.Condition(multiprocessing_manager
556
self.client_structure = [attr for attr in
557
self.__dict__.iterkeys()
558
if not attr.startswith("_")]
559
self.client_structure.append("client_structure")
561
for name, t in inspect.getmembers(type(self),
565
if not name.startswith("_"):
566
self.client_structure.append(name)
568
# Send notice to process children that client state has changed
569
def send_changedstate(self):
570
with self.changedstate:
571
self.changedstate.notify_all()
315
self.last_connect = None
573
317
def enable(self):
574
318
"""Start this client's checker and timeout hooks"""
575
if getattr(self, "enabled", False):
319
if getattr(self, u"enabled", False):
576
320
# Already enabled
578
self.expires = datetime.datetime.utcnow() + self.timeout
580
322
self.last_enabled = datetime.datetime.utcnow()
582
self.send_changedstate()
584
def disable(self, quiet=True):
585
"""Disable this client."""
586
if not getattr(self, "enabled", False):
589
logger.info("Disabling client %s", self.name)
590
if getattr(self, "disable_initiator_tag", None) is not None:
591
gobject.source_remove(self.disable_initiator_tag)
592
self.disable_initiator_tag = None
594
if getattr(self, "checker_initiator_tag", None) is not None:
595
gobject.source_remove(self.checker_initiator_tag)
596
self.checker_initiator_tag = None
600
self.send_changedstate()
601
# Do not run this again if called by a gobject.timeout_add
607
def init_checker(self):
608
323
# Schedule a new checker to be started an 'interval' from now,
609
324
# and every interval from then on.
610
if self.checker_initiator_tag is not None:
611
gobject.source_remove(self.checker_initiator_tag)
612
325
self.checker_initiator_tag = (gobject.timeout_add
613
326
(self.interval_milliseconds(),
614
327
self.start_checker))
615
328
# Schedule a disable() when 'timeout' has passed
616
if self.disable_initiator_tag is not None:
617
gobject.source_remove(self.disable_initiator_tag)
618
329
self.disable_initiator_tag = (gobject.timeout_add
619
330
(self.timeout_milliseconds(),
621
333
# Also start a new checker *right now*.
622
334
self.start_checker()
336
def disable(self, quiet=True):
337
"""Disable this client."""
338
if not getattr(self, "enabled", False):
341
logger.info(u"Disabling client %s", self.name)
342
if getattr(self, u"disable_initiator_tag", False):
343
gobject.source_remove(self.disable_initiator_tag)
344
self.disable_initiator_tag = None
345
if getattr(self, u"checker_initiator_tag", False):
346
gobject.source_remove(self.checker_initiator_tag)
347
self.checker_initiator_tag = None
349
if self.disable_hook:
350
self.disable_hook(self)
352
# Do not run this again if called by a gobject.timeout_add
356
self.disable_hook = None
624
359
def checker_callback(self, pid, condition, command):
625
360
"""The checker has completed, so take appropriate actions."""
626
361
self.checker_callback_tag = None
627
362
self.checker = None
628
363
if os.WIFEXITED(condition):
629
self.last_checker_status = os.WEXITSTATUS(condition)
630
if self.last_checker_status == 0:
631
logger.info("Checker for %(name)s succeeded",
364
exitstatus = os.WEXITSTATUS(condition)
366
logger.info(u"Checker for %(name)s succeeded",
633
368
self.checked_ok()
635
logger.info("Checker for %(name)s failed",
370
logger.info(u"Checker for %(name)s failed",
638
self.last_checker_status = -1
639
logger.warning("Checker for %(name)s crashed?",
373
logger.warning(u"Checker for %(name)s crashed?",
642
376
def checked_ok(self):
643
"""Assert that the client has been seen, alive and well."""
377
"""Bump up the timeout for this client.
379
This should only be called when the client has been seen,
644
382
self.last_checked_ok = datetime.datetime.utcnow()
645
self.last_checker_status = 0
648
def bump_timeout(self, timeout=None):
649
"""Bump up the timeout for this client."""
651
timeout = self.timeout
652
if self.disable_initiator_tag is not None:
653
gobject.source_remove(self.disable_initiator_tag)
654
self.disable_initiator_tag = None
655
if getattr(self, "enabled", False):
656
self.disable_initiator_tag = (gobject.timeout_add
657
(timedelta_to_milliseconds
658
(timeout), self.disable))
659
self.expires = datetime.datetime.utcnow() + timeout
661
def need_approval(self):
662
self.last_approval_request = datetime.datetime.utcnow()
383
gobject.source_remove(self.disable_initiator_tag)
384
self.disable_initiator_tag = (gobject.timeout_add
385
(self.timeout_milliseconds(),
664
388
def start_checker(self):
665
389
"""Start a new checker subprocess if one is not running.
836
534
class DBusObjectWithProperties(dbus.service.Object):
837
535
"""A D-Bus object with properties.
839
537
Classes inheriting from this can use the dbus_service_property
840
538
decorator to expose methods as D-Bus properties. It exposes the
841
539
standard Get(), Set(), and GetAll() methods on the D-Bus.
845
def _is_dbus_thing(thing):
846
"""Returns a function testing if an attribute is a D-Bus thing
848
If called like _is_dbus_thing("method") it returns a function
849
suitable for use as predicate to inspect.getmembers().
851
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
543
def _is_dbus_property(obj):
544
return getattr(obj, u"_dbus_is_property", False)
854
def _get_all_dbus_things(self, thing):
546
def _get_all_dbus_properties(self):
855
547
"""Returns a generator of (name, attribute) pairs
857
return ((getattr(athing.__get__(self), "_dbus_name",
859
athing.__get__(self))
860
for cls in self.__class__.__mro__
862
inspect.getmembers(cls,
863
self._is_dbus_thing(thing)))
549
return ((prop._dbus_name, prop)
551
inspect.getmembers(self, self._is_dbus_property))
865
553
def _get_dbus_property(self, interface_name, property_name):
866
554
"""Returns a bound method if one exists which is a D-Bus
867
555
property with the specified name and interface.
869
for cls in self.__class__.__mro__:
870
for name, value in (inspect.getmembers
872
self._is_dbus_thing("property"))):
873
if (value._dbus_name == property_name
874
and value._dbus_interface == interface_name):
875
return value.__get__(self)
557
for name in (property_name,
558
property_name + u"_dbus_property"):
559
prop = getattr(self, name, None)
561
or not self._is_dbus_property(prop)
562
or prop._dbus_name != property_name
563
or (interface_name and prop._dbus_interface
564
and interface_name != prop._dbus_interface)):
877
567
# No such property
878
raise DBusPropertyNotFound(self.dbus_object_path + ":"
879
+ interface_name + "."
568
raise DBusPropertyNotFound(self.dbus_object_path + u":"
569
+ interface_name + u"."
882
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
572
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
884
574
def Get(self, interface_name, property_name):
885
575
"""Standard D-Bus property Get() method, see D-Bus standard.
887
577
prop = self._get_dbus_property(interface_name, property_name)
888
if prop._dbus_access == "write":
578
if prop._dbus_access == u"write":
889
579
raise DBusPropertyAccessException(property_name)
891
if not hasattr(value, "variant_level"):
581
if not hasattr(value, u"variant_level"):
893
583
return type(value)(value, variant_level=value.variant_level+1)
895
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
585
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
896
586
def Set(self, interface_name, property_name, value):
897
587
"""Standard D-Bus property Set() method, see D-Bus standard.
899
589
prop = self._get_dbus_property(interface_name, property_name)
900
if prop._dbus_access == "read":
590
if prop._dbus_access == u"read":
901
591
raise DBusPropertyAccessException(property_name)
902
if prop._dbus_get_args_options["byte_arrays"]:
903
# The byte_arrays option is not supported yet on
904
# signatures other than "ay".
905
if prop._dbus_signature != "ay":
907
value = dbus.ByteArray(b''.join(chr(byte)
592
if prop._dbus_get_args_options[u"byte_arrays"]:
593
value = dbus.ByteArray(''.join(unichr(byte)
911
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
912
out_signature="a{sv}")
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
598
out_signature=u"a{sv}")
913
599
def GetAll(self, interface_name):
914
600
"""Standard D-Bus property GetAll() method, see D-Bus
917
603
Note: Will not include properties with access="write".
920
for name, prop in self._get_all_dbus_things("property"):
606
for name, prop in self._get_all_dbus_properties():
921
607
if (interface_name
922
608
and interface_name != prop._dbus_interface):
923
609
# Interface non-empty but did not match
925
611
# Ignore write-only properties
926
if prop._dbus_access == "write":
612
if prop._dbus_access == u"write":
929
if not hasattr(value, "variant_level"):
930
properties[name] = value
615
if not hasattr(value, u"variant_level"):
932
properties[name] = type(value)(value, variant_level=
933
value.variant_level+1)
934
return dbus.Dictionary(properties, signature="sv")
618
all[name] = type(value)(value, variant_level=
619
value.variant_level+1)
620
return dbus.Dictionary(all, signature=u"sv")
936
622
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
938
624
path_keyword='object_path',
939
625
connection_keyword='connection')
940
626
def Introspect(self, object_path, connection):
941
"""Overloading of standard D-Bus method.
943
Inserts property tags and interface annotation tags.
627
"""Standard D-Bus method, overloaded to insert property tags.
945
629
xmlstring = dbus.service.Object.Introspect(self, object_path,
948
632
document = xml.dom.minidom.parseString(xmlstring)
949
633
def make_tag(document, name, prop):
950
e = document.createElement("property")
951
e.setAttribute("name", name)
952
e.setAttribute("type", prop._dbus_signature)
953
e.setAttribute("access", prop._dbus_access)
634
e = document.createElement(u"property")
635
e.setAttribute(u"name", name)
636
e.setAttribute(u"type", prop._dbus_signature)
637
e.setAttribute(u"access", prop._dbus_access)
955
for if_tag in document.getElementsByTagName("interface"):
639
for if_tag in document.getElementsByTagName(u"interface"):
957
640
for tag in (make_tag(document, name, prop)
959
in self._get_all_dbus_things("property")
642
in self._get_all_dbus_properties()
960
643
if prop._dbus_interface
961
== if_tag.getAttribute("name")):
644
== if_tag.getAttribute(u"name")):
962
645
if_tag.appendChild(tag)
963
# Add annotation tags
964
for typ in ("method", "signal", "property"):
965
for tag in if_tag.getElementsByTagName(typ):
967
for name, prop in (self.
968
_get_all_dbus_things(typ)):
969
if (name == tag.getAttribute("name")
970
and prop._dbus_interface
971
== if_tag.getAttribute("name")):
972
annots.update(getattr
976
for name, value in annots.iteritems():
977
ann_tag = document.createElement(
979
ann_tag.setAttribute("name", name)
980
ann_tag.setAttribute("value", value)
981
tag.appendChild(ann_tag)
982
# Add interface annotation tags
983
for annotation, value in dict(
984
itertools.chain.from_iterable(
985
annotations().iteritems()
986
for name, annotations in
987
self._get_all_dbus_things("interface")
988
if name == if_tag.getAttribute("name")
990
ann_tag = document.createElement("annotation")
991
ann_tag.setAttribute("name", annotation)
992
ann_tag.setAttribute("value", value)
993
if_tag.appendChild(ann_tag)
994
646
# Add the names to the return values for the
995
647
# "org.freedesktop.DBus.Properties" methods
996
if (if_tag.getAttribute("name")
997
== "org.freedesktop.DBus.Properties"):
998
for cn in if_tag.getElementsByTagName("method"):
999
if cn.getAttribute("name") == "Get":
1000
for arg in cn.getElementsByTagName("arg"):
1001
if (arg.getAttribute("direction")
1003
arg.setAttribute("name", "value")
1004
elif cn.getAttribute("name") == "GetAll":
1005
for arg in cn.getElementsByTagName("arg"):
1006
if (arg.getAttribute("direction")
1008
arg.setAttribute("name", "props")
1009
xmlstring = document.toxml("utf-8")
648
if (if_tag.getAttribute(u"name")
649
== u"org.freedesktop.DBus.Properties"):
650
for cn in if_tag.getElementsByTagName(u"method"):
651
if cn.getAttribute(u"name") == u"Get":
652
for arg in cn.getElementsByTagName(u"arg"):
653
if (arg.getAttribute(u"direction")
655
arg.setAttribute(u"name", u"value")
656
elif cn.getAttribute(u"name") == u"GetAll":
657
for arg in cn.getElementsByTagName(u"arg"):
658
if (arg.getAttribute(u"direction")
660
arg.setAttribute(u"name", u"props")
661
xmlstring = document.toxml(u"utf-8")
1010
662
document.unlink()
1011
663
except (AttributeError, xml.dom.DOMException,
1012
xml.parsers.expat.ExpatError) as error:
1013
logger.error("Failed to override Introspection method",
664
xml.parsers.expat.ExpatError), error:
665
logger.error(u"Failed to override Introspection method",
1015
667
return xmlstring
1018
def datetime_to_dbus (dt, variant_level=0):
1019
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1021
return dbus.String("", variant_level = variant_level)
1022
return dbus.String(dt.isoformat(),
1023
variant_level=variant_level)
1026
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1027
"""A class decorator; applied to a subclass of
1028
dbus.service.Object, it will add alternate D-Bus attributes with
1029
interface names according to the "alt_interface_names" mapping.
1032
@alternate_dbus_names({"org.example.Interface":
1033
"net.example.AlternateInterface"})
1034
class SampleDBusObject(dbus.service.Object):
1035
@dbus.service.method("org.example.Interface")
1036
def SampleDBusMethod():
1039
The above "SampleDBusMethod" on "SampleDBusObject" will be
1040
reachable via two interfaces: "org.example.Interface" and
1041
"net.example.AlternateInterface", the latter of which will have
1042
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1043
"true", unless "deprecate" is passed with a False value.
1045
This works for methods and signals, and also for D-Bus properties
1046
(from DBusObjectWithProperties) and interfaces (from the
1047
dbus_interface_annotations decorator).
1050
for orig_interface_name, alt_interface_name in (
1051
alt_interface_names.iteritems()):
1053
interface_names = set()
1054
# Go though all attributes of the class
1055
for attrname, attribute in inspect.getmembers(cls):
1056
# Ignore non-D-Bus attributes, and D-Bus attributes
1057
# with the wrong interface name
1058
if (not hasattr(attribute, "_dbus_interface")
1059
or not attribute._dbus_interface
1060
.startswith(orig_interface_name)):
1062
# Create an alternate D-Bus interface name based on
1064
alt_interface = (attribute._dbus_interface
1065
.replace(orig_interface_name,
1066
alt_interface_name))
1067
interface_names.add(alt_interface)
1068
# Is this a D-Bus signal?
1069
if getattr(attribute, "_dbus_is_signal", False):
1070
# Extract the original non-method function by
1072
nonmethod_func = (dict(
1073
zip(attribute.func_code.co_freevars,
1074
attribute.__closure__))["func"]
1076
# Create a new, but exactly alike, function
1077
# object, and decorate it to be a new D-Bus signal
1078
# with the alternate D-Bus interface name
1079
new_function = (dbus.service.signal
1081
attribute._dbus_signature)
1082
(types.FunctionType(
1083
nonmethod_func.func_code,
1084
nonmethod_func.func_globals,
1085
nonmethod_func.func_name,
1086
nonmethod_func.func_defaults,
1087
nonmethod_func.func_closure)))
1088
# Copy annotations, if any
1090
new_function._dbus_annotations = (
1091
dict(attribute._dbus_annotations))
1092
except AttributeError:
1094
# Define a creator of a function to call both the
1095
# original and alternate functions, so both the
1096
# original and alternate signals gets sent when
1097
# the function is called
1098
def fixscope(func1, func2):
1099
"""This function is a scope container to pass
1100
func1 and func2 to the "call_both" function
1101
outside of its arguments"""
1102
def call_both(*args, **kwargs):
1103
"""This function will emit two D-Bus
1104
signals by calling func1 and func2"""
1105
func1(*args, **kwargs)
1106
func2(*args, **kwargs)
1108
# Create the "call_both" function and add it to
1110
attr[attrname] = fixscope(attribute, new_function)
1111
# Is this a D-Bus method?
1112
elif getattr(attribute, "_dbus_is_method", False):
1113
# Create a new, but exactly alike, function
1114
# object. Decorate it to be a new D-Bus method
1115
# with the alternate D-Bus interface name. Add it
1117
attr[attrname] = (dbus.service.method
1119
attribute._dbus_in_signature,
1120
attribute._dbus_out_signature)
1122
(attribute.func_code,
1123
attribute.func_globals,
1124
attribute.func_name,
1125
attribute.func_defaults,
1126
attribute.func_closure)))
1127
# Copy annotations, if any
1129
attr[attrname]._dbus_annotations = (
1130
dict(attribute._dbus_annotations))
1131
except AttributeError:
1133
# Is this a D-Bus property?
1134
elif getattr(attribute, "_dbus_is_property", False):
1135
# Create a new, but exactly alike, function
1136
# object, and decorate it to be a new D-Bus
1137
# property with the alternate D-Bus interface
1138
# name. Add it to the class.
1139
attr[attrname] = (dbus_service_property
1141
attribute._dbus_signature,
1142
attribute._dbus_access,
1144
._dbus_get_args_options
1147
(attribute.func_code,
1148
attribute.func_globals,
1149
attribute.func_name,
1150
attribute.func_defaults,
1151
attribute.func_closure)))
1152
# Copy annotations, if any
1154
attr[attrname]._dbus_annotations = (
1155
dict(attribute._dbus_annotations))
1156
except AttributeError:
1158
# Is this a D-Bus interface?
1159
elif getattr(attribute, "_dbus_is_interface", False):
1160
# Create a new, but exactly alike, function
1161
# object. Decorate it to be a new D-Bus interface
1162
# with the alternate D-Bus interface name. Add it
1164
attr[attrname] = (dbus_interface_annotations
1167
(attribute.func_code,
1168
attribute.func_globals,
1169
attribute.func_name,
1170
attribute.func_defaults,
1171
attribute.func_closure)))
1173
# Deprecate all alternate interfaces
1174
iname="_AlternateDBusNames_interface_annotation{0}"
1175
for interface_name in interface_names:
1176
@dbus_interface_annotations(interface_name)
1178
return { "org.freedesktop.DBus.Deprecated":
1180
# Find an unused name
1181
for aname in (iname.format(i)
1182
for i in itertools.count()):
1183
if aname not in attr:
1187
# Replace the class with a new subclass of it with
1188
# methods, signals, etc. as created above.
1189
cls = type(b"{0}Alternate".format(cls.__name__),
1195
@alternate_dbus_interfaces({"se.recompile.Mandos":
1196
"se.bsnet.fukt.Mandos"})
1197
670
class ClientDBus(Client, DBusObjectWithProperties):
1198
671
"""A Client class using D-Bus
1212
681
Client.__init__(self, *args, **kwargs)
1213
682
# Only now, when this client is initialized, can it show up on
1215
client_object_name = unicode(self.name).translate(
1216
{ord("."): ord("_"),
1217
ord("-"): ord("_")})
1218
684
self.dbus_object_path = (dbus.ObjectPath
1219
("/clients/" + client_object_name))
686
+ self.name.replace(u".", u"_")))
1220
687
DBusObjectWithProperties.__init__(self, self.bus,
1221
688
self.dbus_object_path)
1223
def notifychangeproperty(transform_func,
1224
dbus_name, type_func=lambda x: x,
1226
""" Modify a variable so that it's a property which announces
1227
its changes to DBus.
1229
transform_fun: Function that takes a value and a variant_level
1230
and transforms it to a D-Bus type.
1231
dbus_name: D-Bus name of the variable
1232
type_func: Function that transform the value before sending it
1233
to the D-Bus. Default: no transform
1234
variant_level: D-Bus variant level. Default: 1
1236
attrname = "_{0}".format(dbus_name)
1237
def setter(self, value):
1238
if hasattr(self, "dbus_object_path"):
1239
if (not hasattr(self, attrname) or
1240
type_func(getattr(self, attrname, None))
1241
!= type_func(value)):
1242
dbus_value = transform_func(type_func(value),
1245
self.PropertyChanged(dbus.String(dbus_name),
1247
setattr(self, attrname, value)
1249
return property(lambda self: getattr(self, attrname), setter)
1251
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1252
approvals_pending = notifychangeproperty(dbus.Boolean,
1255
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1256
last_enabled = notifychangeproperty(datetime_to_dbus,
1258
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1259
type_func = lambda checker:
1260
checker is not None)
1261
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1263
last_checker_status = notifychangeproperty(dbus.Int16,
1264
"LastCheckerStatus")
1265
last_approval_request = notifychangeproperty(
1266
datetime_to_dbus, "LastApprovalRequest")
1267
approved_by_default = notifychangeproperty(dbus.Boolean,
1268
"ApprovedByDefault")
1269
approval_delay = notifychangeproperty(dbus.UInt64,
1272
timedelta_to_milliseconds)
1273
approval_duration = notifychangeproperty(
1274
dbus.UInt64, "ApprovalDuration",
1275
type_func = timedelta_to_milliseconds)
1276
host = notifychangeproperty(dbus.String, "Host")
1277
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1279
timedelta_to_milliseconds)
1280
extended_timeout = notifychangeproperty(
1281
dbus.UInt64, "ExtendedTimeout",
1282
type_func = timedelta_to_milliseconds)
1283
interval = notifychangeproperty(dbus.UInt64,
1286
timedelta_to_milliseconds)
1287
checker_command = notifychangeproperty(dbus.String, "Checker")
1289
del notifychangeproperty
691
def _datetime_to_dbus(dt, variant_level=0):
692
"""Convert a UTC datetime.datetime() to a D-Bus type."""
693
return dbus.String(dt.isoformat(),
694
variant_level=variant_level)
697
oldstate = getattr(self, u"enabled", False)
698
r = Client.enable(self)
699
if oldstate != self.enabled:
701
self.PropertyChanged(dbus.String(u"enabled"),
702
dbus.Boolean(True, variant_level=1))
703
self.PropertyChanged(
704
dbus.String(u"last_enabled"),
705
self._datetime_to_dbus(self.last_enabled,
709
def disable(self, quiet = False):
710
oldstate = getattr(self, u"enabled", False)
711
r = Client.disable(self, quiet=quiet)
712
if not quiet and oldstate != self.enabled:
714
self.PropertyChanged(dbus.String(u"enabled"),
715
dbus.Boolean(False, variant_level=1))
1291
718
def __del__(self, *args, **kwargs):
1293
720
self.remove_from_connection()
1294
721
except LookupError:
1296
if hasattr(DBusObjectWithProperties, "__del__"):
723
if hasattr(DBusObjectWithProperties, u"__del__"):
1297
724
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1298
725
Client.__del__(self, *args, **kwargs)
1427
842
def StopChecker(self):
1428
843
self.stop_checker()
1432
# ApprovalPending - property
1433
@dbus_service_property(_interface, signature="b", access="read")
1434
def ApprovalPending_dbus_property(self):
1435
return dbus.Boolean(bool(self.approvals_pending))
1437
# ApprovedByDefault - property
1438
@dbus_service_property(_interface, signature="b",
1440
def ApprovedByDefault_dbus_property(self, value=None):
1441
if value is None: # get
1442
return dbus.Boolean(self.approved_by_default)
1443
self.approved_by_default = bool(value)
1445
# ApprovalDelay - property
1446
@dbus_service_property(_interface, signature="t",
1448
def ApprovalDelay_dbus_property(self, value=None):
1449
if value is None: # get
1450
return dbus.UInt64(self.approval_delay_milliseconds())
1451
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1453
# ApprovalDuration - property
1454
@dbus_service_property(_interface, signature="t",
1456
def ApprovalDuration_dbus_property(self, value=None):
1457
if value is None: # get
1458
return dbus.UInt64(timedelta_to_milliseconds(
1459
self.approval_duration))
1460
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1463
@dbus_service_property(_interface, signature="s", access="read")
1464
def Name_dbus_property(self):
846
@dbus_service_property(_interface, signature=u"s", access=u"read")
847
def name_dbus_property(self):
1465
848
return dbus.String(self.name)
1467
# Fingerprint - property
1468
@dbus_service_property(_interface, signature="s", access="read")
1469
def Fingerprint_dbus_property(self):
850
# fingerprint - property
851
@dbus_service_property(_interface, signature=u"s", access=u"read")
852
def fingerprint_dbus_property(self):
1470
853
return dbus.String(self.fingerprint)
1473
@dbus_service_property(_interface, signature="s",
1475
def Host_dbus_property(self, value=None):
856
@dbus_service_property(_interface, signature=u"s",
858
def host_dbus_property(self, value=None):
1476
859
if value is None: # get
1477
860
return dbus.String(self.host)
1478
self.host = unicode(value)
1480
# Created - property
1481
@dbus_service_property(_interface, signature="s", access="read")
1482
def Created_dbus_property(self):
1483
return datetime_to_dbus(self.created)
1485
# LastEnabled - property
1486
@dbus_service_property(_interface, signature="s", access="read")
1487
def LastEnabled_dbus_property(self):
1488
return datetime_to_dbus(self.last_enabled)
1490
# Enabled - property
1491
@dbus_service_property(_interface, signature="b",
1493
def Enabled_dbus_property(self, value=None):
863
self.PropertyChanged(dbus.String(u"host"),
864
dbus.String(value, variant_level=1))
867
@dbus_service_property(_interface, signature=u"s", access=u"read")
868
def created_dbus_property(self):
869
return dbus.String(self._datetime_to_dbus(self.created))
871
# last_enabled - property
872
@dbus_service_property(_interface, signature=u"s", access=u"read")
873
def last_enabled_dbus_property(self):
874
if self.last_enabled is None:
875
return dbus.String(u"")
876
return dbus.String(self._datetime_to_dbus(self.last_enabled))
879
@dbus_service_property(_interface, signature=u"b",
881
def enabled_dbus_property(self, value=None):
1494
882
if value is None: # get
1495
883
return dbus.Boolean(self.enabled)
1501
# LastCheckedOK - property
1502
@dbus_service_property(_interface, signature="s",
1504
def LastCheckedOK_dbus_property(self, value=None):
889
# last_checked_ok - property
890
@dbus_service_property(_interface, signature=u"s",
892
def last_checked_ok_dbus_property(self, value=None):
1505
893
if value is not None:
1506
894
self.checked_ok()
1508
return datetime_to_dbus(self.last_checked_ok)
1510
# LastCheckerStatus - property
1511
@dbus_service_property(_interface, signature="n",
1513
def LastCheckerStatus_dbus_property(self):
1514
return dbus.Int16(self.last_checker_status)
1516
# Expires - property
1517
@dbus_service_property(_interface, signature="s", access="read")
1518
def Expires_dbus_property(self):
1519
return datetime_to_dbus(self.expires)
1521
# LastApprovalRequest - property
1522
@dbus_service_property(_interface, signature="s", access="read")
1523
def LastApprovalRequest_dbus_property(self):
1524
return datetime_to_dbus(self.last_approval_request)
1526
# Timeout - property
1527
@dbus_service_property(_interface, signature="t",
1529
def Timeout_dbus_property(self, value=None):
896
if self.last_checked_ok is None:
897
return dbus.String(u"")
898
return dbus.String(self._datetime_to_dbus(self
902
@dbus_service_property(_interface, signature=u"t",
904
def timeout_dbus_property(self, value=None):
1530
905
if value is None: # get
1531
906
return dbus.UInt64(self.timeout_milliseconds())
1532
old_timeout = self.timeout
1533
907
self.timeout = datetime.timedelta(0, 0, 0, value)
1534
# Reschedule disabling
1536
now = datetime.datetime.utcnow()
1537
self.expires += self.timeout - old_timeout
1538
if self.expires <= now:
1539
# The timeout has passed
1542
if (getattr(self, "disable_initiator_tag", None)
1545
gobject.source_remove(self.disable_initiator_tag)
1546
self.disable_initiator_tag = (
1547
gobject.timeout_add(
1548
timedelta_to_milliseconds(self.expires - now),
1551
# ExtendedTimeout - property
1552
@dbus_service_property(_interface, signature="t",
1554
def ExtendedTimeout_dbus_property(self, value=None):
1555
if value is None: # get
1556
return dbus.UInt64(self.extended_timeout_milliseconds())
1557
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1559
# Interval - property
1560
@dbus_service_property(_interface, signature="t",
1562
def Interval_dbus_property(self, value=None):
909
self.PropertyChanged(dbus.String(u"timeout"),
910
dbus.UInt64(value, variant_level=1))
911
if getattr(self, u"disable_initiator_tag", None) is None:
914
gobject.source_remove(self.disable_initiator_tag)
915
self.disable_initiator_tag = None
917
_timedelta_to_milliseconds((self
923
# The timeout has passed
926
self.disable_initiator_tag = (gobject.timeout_add
927
(time_to_die, self.disable))
929
# interval - property
930
@dbus_service_property(_interface, signature=u"t",
932
def interval_dbus_property(self, value=None):
1563
933
if value is None: # get
1564
934
return dbus.UInt64(self.interval_milliseconds())
1565
935
self.interval = datetime.timedelta(0, 0, 0, value)
1566
if getattr(self, "checker_initiator_tag", None) is None:
937
self.PropertyChanged(dbus.String(u"interval"),
938
dbus.UInt64(value, variant_level=1))
939
if getattr(self, u"checker_initiator_tag", None) is None:
1569
# Reschedule checker run
1570
gobject.source_remove(self.checker_initiator_tag)
1571
self.checker_initiator_tag = (gobject.timeout_add
1572
(value, self.start_checker))
1573
self.start_checker() # Start one now, too
1575
# Checker - property
1576
@dbus_service_property(_interface, signature="s",
1578
def Checker_dbus_property(self, value=None):
941
# Reschedule checker run
942
gobject.source_remove(self.checker_initiator_tag)
943
self.checker_initiator_tag = (gobject.timeout_add
944
(value, self.start_checker))
945
self.start_checker() # Start one now, too
948
@dbus_service_property(_interface, signature=u"s",
950
def checker_dbus_property(self, value=None):
1579
951
if value is None: # get
1580
952
return dbus.String(self.checker_command)
1581
self.checker_command = unicode(value)
953
self.checker_command = value
955
self.PropertyChanged(dbus.String(u"checker"),
956
dbus.String(self.checker_command,
1583
# CheckerRunning - property
1584
@dbus_service_property(_interface, signature="b",
1586
def CheckerRunning_dbus_property(self, value=None):
959
# checker_running - property
960
@dbus_service_property(_interface, signature=u"b",
962
def checker_running_dbus_property(self, value=None):
1587
963
if value is None: # get
1588
964
return dbus.Boolean(self.checker is not None)
1638
988
Note: This will run in its own forked process."""
1640
990
def handle(self):
1641
with contextlib.closing(self.server.child_pipe) as child_pipe:
1642
logger.info("TCP connection from: %s",
1643
unicode(self.client_address))
1644
logger.debug("Pipe FD: %d",
1645
self.server.child_pipe.fileno())
991
logger.info(u"TCP connection from: %s",
992
unicode(self.client_address))
993
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
994
# Open IPC pipe to parent process
995
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1647
996
session = (gnutls.connection
1648
997
.ClientSession(self.request,
1649
998
gnutls.connection
1650
999
.X509Credentials()))
1001
line = self.request.makefile().readline()
1002
logger.debug(u"Protocol version: %r", line)
1004
if int(line.strip().split()[0]) > 1:
1006
except (ValueError, IndexError, RuntimeError), error:
1007
logger.error(u"Unknown protocol version: %s", error)
1652
1010
# Note: gnutls.connection.X509Credentials is really a
1653
1011
# generic GnuTLS certificate credentials object so long as
1654
1012
# no X.509 keys are added to it. Therefore, we can use it
1655
1013
# here despite using OpenPGP certificates.
1657
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1658
# "+AES-256-CBC", "+SHA1",
1659
# "+COMP-NULL", "+CTYPE-OPENPGP",
1015
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1016
# u"+AES-256-CBC", u"+SHA1",
1017
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1661
1019
# Use a fallback default, since this MUST be set.
1662
1020
priority = self.server.gnutls_priority
1663
1021
if priority is None:
1022
priority = u"NORMAL"
1665
1023
(gnutls.library.functions
1666
1024
.gnutls_priority_set_direct(session._c_object,
1667
1025
priority, None))
1669
# Start communication using the Mandos protocol
1670
# Get protocol number
1671
line = self.request.makefile().readline()
1672
logger.debug("Protocol version: %r", line)
1674
if int(line.strip().split()[0]) > 1:
1676
except (ValueError, IndexError, RuntimeError) as error:
1677
logger.error("Unknown protocol version: %s", error)
1680
# Start GnuTLS connection
1682
1028
session.handshake()
1683
except gnutls.errors.GNUTLSError as error:
1684
logger.warning("Handshake failed: %s", error)
1029
except gnutls.errors.GNUTLSError, error:
1030
logger.warning(u"Handshake failed: %s", error)
1685
1031
# Do not run session.bye() here: the session is not
1686
1032
# established. Just abandon the request.
1688
logger.debug("Handshake succeeded")
1690
approval_required = False
1034
logger.debug(u"Handshake succeeded")
1693
fpr = self.fingerprint(self.peer_certificate
1696
gnutls.errors.GNUTLSError) as error:
1697
logger.warning("Bad certificate: %s", error)
1699
logger.debug("Fingerprint: %s", fpr)
1702
client = ProxyClient(child_pipe, fpr,
1703
self.client_address)
1707
if client.approval_delay:
1708
delay = client.approval_delay
1709
client.approvals_pending += 1
1710
approval_required = True
1713
if not client.enabled:
1714
logger.info("Client %s is disabled",
1716
if self.server.use_dbus:
1718
client.Rejected("Disabled")
1721
if client.approved or not client.approval_delay:
1722
#We are approved or approval is disabled
1724
elif client.approved is None:
1725
logger.info("Client %s needs approval",
1727
if self.server.use_dbus:
1729
client.NeedApproval(
1730
client.approval_delay_milliseconds(),
1731
client.approved_by_default)
1733
logger.warning("Client %s was not approved",
1735
if self.server.use_dbus:
1737
client.Rejected("Denied")
1740
#wait until timeout or approved
1741
time = datetime.datetime.now()
1742
client.changedstate.acquire()
1743
client.changedstate.wait(
1744
float(timedelta_to_milliseconds(delay)
1746
client.changedstate.release()
1747
time2 = datetime.datetime.now()
1748
if (time2 - time) >= delay:
1749
if not client.approved_by_default:
1750
logger.warning("Client %s timed out while"
1751
" waiting for approval",
1753
if self.server.use_dbus:
1755
client.Rejected("Approval timed out")
1760
delay -= time2 - time
1763
while sent_size < len(client.secret):
1765
sent = session.send(client.secret[sent_size:])
1766
except gnutls.errors.GNUTLSError as error:
1767
logger.warning("gnutls send failed",
1770
logger.debug("Sent: %d, remaining: %d",
1771
sent, len(client.secret)
1772
- (sent_size + sent))
1775
logger.info("Sending secret to %s", client.name)
1776
# bump the timeout using extended_timeout
1777
client.bump_timeout(client.extended_timeout)
1778
if self.server.use_dbus:
1036
fpr = self.fingerprint(self.peer_certificate(session))
1037
except (TypeError, gnutls.errors.GNUTLSError), error:
1038
logger.warning(u"Bad certificate: %s", error)
1041
logger.debug(u"Fingerprint: %s", fpr)
1783
if approval_required:
1784
client.approvals_pending -= 1
1787
except gnutls.errors.GNUTLSError as error:
1788
logger.warning("GnuTLS bye failed",
1043
for c in self.server.clients:
1044
if c.fingerprint == fpr:
1048
ipc.write(u"NOTFOUND %s %s\n"
1049
% (fpr, unicode(self.client_address)))
1052
# Have to check if client.still_valid(), since it is
1053
# possible that the client timed out while establishing
1054
# the GnuTLS session.
1055
if not client.still_valid():
1056
ipc.write(u"INVALID %s\n" % client.name)
1059
ipc.write(u"SENDING %s\n" % client.name)
1061
while sent_size < len(client.secret):
1062
sent = session.send(client.secret[sent_size:])
1063
logger.debug(u"Sent: %d, remaining: %d",
1064
sent, len(client.secret)
1065
- (sent_size + sent))
1792
1070
def peer_certificate(session):
1981
1236
def server_activate(self):
1982
1237
if self.enabled:
1983
1238
return socketserver.TCPServer.server_activate(self)
1985
1239
def enable(self):
1986
1240
self.enabled = True
1988
def add_pipe(self, parent_pipe, proc):
1241
def add_pipe(self, pipe):
1989
1242
# Call "handle_ipc" for both data and EOF events
1990
gobject.io_add_watch(parent_pipe.fileno(),
1991
gobject.IO_IN | gobject.IO_HUP,
1992
functools.partial(self.handle_ipc,
1997
def handle_ipc(self, source, condition, parent_pipe=None,
1998
proc = None, client_object=None):
1999
# error, or the other end of multiprocessing.Pipe has closed
2000
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2001
# Wait for other process to exit
2005
# Read a request from the child
2006
request = parent_pipe.recv()
2007
command = request[0]
2009
if command == 'init':
2011
address = request[2]
2013
for c in self.clients.itervalues():
2014
if c.fingerprint == fpr:
2018
logger.info("Client not found for fingerprint: %s, ad"
2019
"dress: %s", fpr, address)
2022
mandos_dbus_service.ClientNotFound(fpr,
2024
parent_pipe.send(False)
2027
gobject.io_add_watch(parent_pipe.fileno(),
2028
gobject.IO_IN | gobject.IO_HUP,
2029
functools.partial(self.handle_ipc,
2035
parent_pipe.send(True)
2036
# remove the old hook in favor of the new above hook on
2039
if command == 'funcall':
2040
funcname = request[1]
2044
parent_pipe.send(('data', getattr(client_object,
2048
if command == 'getattr':
2049
attrname = request[1]
2050
if callable(client_object.__getattribute__(attrname)):
2051
parent_pipe.send(('function',))
2053
parent_pipe.send(('data', client_object
2054
.__getattribute__(attrname)))
2056
if command == 'setattr':
2057
attrname = request[1]
2059
setattr(client_object, attrname, value)
1243
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1245
def handle_ipc(self, source, condition, file_objects={}):
1247
gobject.IO_IN: u"IN", # There is data to read.
1248
gobject.IO_OUT: u"OUT", # Data can be written (without
1250
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1251
gobject.IO_ERR: u"ERR", # Error condition.
1252
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1253
# broken, usually for pipes and
1256
conditions_string = ' | '.join(name
1258
condition_names.iteritems()
1259
if cond & condition)
1260
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1263
# Turn the pipe file descriptor into a Python file object
1264
if source not in file_objects:
1265
file_objects[source] = os.fdopen(source, u"r", 1)
1267
# Read a line from the file object
1268
cmdline = file_objects[source].readline()
1269
if not cmdline: # Empty line means end of file
1270
# close the IPC pipe
1271
file_objects[source].close()
1272
del file_objects[source]
1274
# Stop calling this function
1277
logger.debug(u"IPC command: %r", cmdline)
1279
# Parse and act on command
1280
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1282
if cmd == u"NOTFOUND":
1283
fpr, address = args.split(None, 1)
1284
logger.warning(u"Client not found for fingerprint: %s, ad"
1285
u"dress: %s", fpr, address)
1288
mandos_dbus_service.ClientNotFound(fpr, address)
1289
elif cmd == u"INVALID":
1290
for client in self.clients:
1291
if client.name == args:
1292
logger.warning(u"Client %s is invalid", args)
1298
logger.error(u"Unknown client %s is invalid", args)
1299
elif cmd == u"SENDING":
1300
for client in self.clients:
1301
if client.name == args:
1302
logger.info(u"Sending secret to %s", client.name)
1309
logger.error(u"Sending secret to unknown client %s",
1312
logger.error(u"Unknown IPC command: %r", cmdline)
1314
# Keep calling this function
2064
1318
def string_to_delta(interval):
2065
1319
"""Parse a string and return a datetime.timedelta
2067
>>> string_to_delta('7d')
1321
>>> string_to_delta(u'7d')
2068
1322
datetime.timedelta(7)
2069
>>> string_to_delta('60s')
1323
>>> string_to_delta(u'60s')
2070
1324
datetime.timedelta(0, 60)
2071
>>> string_to_delta('60m')
1325
>>> string_to_delta(u'60m')
2072
1326
datetime.timedelta(0, 3600)
2073
>>> string_to_delta('24h')
1327
>>> string_to_delta(u'24h')
2074
1328
datetime.timedelta(1)
2075
>>> string_to_delta('1w')
1329
>>> string_to_delta(u'1w')
2076
1330
datetime.timedelta(7)
2077
>>> string_to_delta('5m 30s')
1331
>>> string_to_delta(u'5m 30s')
2078
1332
datetime.timedelta(0, 330)
2080
1334
timevalue = datetime.timedelta(0)
2131
1408
##################################################################
2132
1409
# Parsing of options, both command line and config file
2134
parser = argparse.ArgumentParser()
2135
parser.add_argument("-v", "--version", action="version",
2136
version = "%(prog)s {0}".format(version),
2137
help="show version number and exit")
2138
parser.add_argument("-i", "--interface", metavar="IF",
2139
help="Bind to interface IF")
2140
parser.add_argument("-a", "--address",
2141
help="Address to listen for requests on")
2142
parser.add_argument("-p", "--port", type=int,
2143
help="Port number to receive requests on")
2144
parser.add_argument("--check", action="store_true",
2145
help="Run self-test")
2146
parser.add_argument("--debug", action="store_true",
2147
help="Debug mode; run in foreground and log"
2149
parser.add_argument("--debuglevel", metavar="LEVEL",
2150
help="Debug level for stdout output")
2151
parser.add_argument("--priority", help="GnuTLS"
2152
" priority string (see GnuTLS documentation)")
2153
parser.add_argument("--servicename",
2154
metavar="NAME", help="Zeroconf service name")
2155
parser.add_argument("--configdir",
2156
default="/etc/mandos", metavar="DIR",
2157
help="Directory to search for configuration"
2159
parser.add_argument("--no-dbus", action="store_false",
2160
dest="use_dbus", help="Do not provide D-Bus"
2161
" system bus interface")
2162
parser.add_argument("--no-ipv6", action="store_false",
2163
dest="use_ipv6", help="Do not use IPv6")
2164
parser.add_argument("--no-restore", action="store_false",
2165
dest="restore", help="Do not restore stored"
2167
parser.add_argument("--statedir", metavar="DIR",
2168
help="Directory to save/restore state in")
2170
options = parser.parse_args()
1411
parser = optparse.OptionParser(version = "%%prog %s" % version)
1412
parser.add_option("-i", u"--interface", type=u"string",
1413
metavar="IF", help=u"Bind to interface IF")
1414
parser.add_option("-a", u"--address", type=u"string",
1415
help=u"Address to listen for requests on")
1416
parser.add_option("-p", u"--port", type=u"int",
1417
help=u"Port number to receive requests on")
1418
parser.add_option("--check", action=u"store_true",
1419
help=u"Run self-test")
1420
parser.add_option("--debug", action=u"store_true",
1421
help=u"Debug mode; run in foreground and log to"
1423
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1424
u" priority string (see GnuTLS documentation)")
1425
parser.add_option("--servicename", type=u"string",
1426
metavar=u"NAME", help=u"Zeroconf service name")
1427
parser.add_option("--configdir", type=u"string",
1428
default=u"/etc/mandos", metavar=u"DIR",
1429
help=u"Directory to search for configuration"
1431
parser.add_option("--no-dbus", action=u"store_false",
1432
dest=u"use_dbus", help=u"Do not provide D-Bus"
1433
u" system bus interface")
1434
parser.add_option("--no-ipv6", action=u"store_false",
1435
dest=u"use_ipv6", help=u"Do not use IPv6")
1436
options = parser.parse_args()[0]
2172
1438
if options.check:
2224
1486
##################################################################
2226
1488
# For convenience
2227
debug = server_settings["debug"]
2228
debuglevel = server_settings["debuglevel"]
2229
use_dbus = server_settings["use_dbus"]
2230
use_ipv6 = server_settings["use_ipv6"]
2231
stored_state_path = os.path.join(server_settings["statedir"],
2235
initlogger(debug, logging.DEBUG)
2240
level = getattr(logging, debuglevel.upper())
2241
initlogger(debug, level)
2243
if server_settings["servicename"] != "Mandos":
1489
debug = server_settings[u"debug"]
1490
use_dbus = server_settings[u"use_dbus"]
1491
use_ipv6 = server_settings[u"use_ipv6"]
1494
syslogger.setLevel(logging.WARNING)
1495
console.setLevel(logging.WARNING)
1497
if server_settings[u"servicename"] != u"Mandos":
2244
1498
syslogger.setFormatter(logging.Formatter
2245
('Mandos ({0}) [%(process)d]:'
2246
' %(levelname)s: %(message)s'
2247
.format(server_settings
1499
(u'Mandos (%s) [%%(process)d]:'
1500
u' %%(levelname)s: %%(message)s'
1501
% server_settings[u"servicename"]))
2250
1503
# Parse config file with clients
2251
client_config = configparser.SafeConfigParser(Client
2253
client_config.read(os.path.join(server_settings["configdir"],
1504
client_defaults = { u"timeout": u"1h",
1506
u"checker": u"fping -q -- %%(host)s",
1509
client_config = configparser.SafeConfigParser(client_defaults)
1510
client_config.read(os.path.join(server_settings[u"configdir"],
2256
1513
global mandos_dbus_service
2257
1514
mandos_dbus_service = None
2259
tcp_server = MandosServer((server_settings["address"],
2260
server_settings["port"]),
1516
tcp_server = MandosServer((server_settings[u"address"],
1517
server_settings[u"port"]),
2262
interface=(server_settings["interface"]
1519
interface=server_settings[u"interface"],
2264
1520
use_ipv6=use_ipv6,
2265
1521
gnutls_priority=
2266
server_settings["priority"],
1522
server_settings[u"priority"],
2267
1523
use_dbus=use_dbus)
2269
pidfilename = "/var/run/mandos.pid"
2271
pidfile = open(pidfilename, "w")
2272
except IOError as e:
2273
logger.error("Could not open file %r", pidfilename,
1524
pidfilename = u"/var/run/mandos.pid"
1526
pidfile = open(pidfilename, u"w")
1528
logger.error(u"Could not open file %r", pidfilename)
2276
for name in ("_mandos", "mandos", "nobody"):
1531
uid = pwd.getpwnam(u"_mandos").pw_uid
1532
gid = pwd.getpwnam(u"_mandos").pw_gid
2278
uid = pwd.getpwnam(name).pw_uid
2279
gid = pwd.getpwnam(name).pw_gid
1535
uid = pwd.getpwnam(u"mandos").pw_uid
1536
gid = pwd.getpwnam(u"mandos").pw_gid
2281
1537
except KeyError:
1539
uid = pwd.getpwnam(u"nobody").pw_uid
1540
gid = pwd.getpwnam(u"nobody").pw_gid
2289
except OSError as error:
2290
if error.errno != errno.EPERM:
1547
except OSError, error:
1548
if error[0] != errno.EPERM:
1551
# Enable all possible GnuTLS debugging
2294
# Enable all possible GnuTLS debugging
2296
1553
# "Use a log level over 10 to enable all debugging options."
2297
1554
# - GnuTLS manual
2298
1555
gnutls.library.functions.gnutls_global_set_log_level(11)
2300
1557
@gnutls.library.types.gnutls_log_func
2301
1558
def debug_gnutls(level, string):
2302
logger.debug("GnuTLS: %s", string[:-1])
1559
logger.debug(u"GnuTLS: %s", string[:-1])
2304
1561
(gnutls.library.functions
2305
1562
.gnutls_global_set_log_function(debug_gnutls))
2307
# Redirect stdin so all checkers get /dev/null
2308
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2309
os.dup2(null, sys.stdin.fileno())
2313
# Need to fork before connecting to D-Bus
2315
# Close all input and output, do double fork, etc.
2318
gobject.threads_init()
2320
1564
global main_loop
2321
1565
# From the Avahi example code
2322
DBusGMainLoop(set_as_default=True)
1566
DBusGMainLoop(set_as_default=True )
2323
1567
main_loop = gobject.MainLoop()
2324
1568
bus = dbus.SystemBus()
2325
1569
# End of Avahi example code
2328
bus_name = dbus.service.BusName("se.recompile.Mandos",
1572
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2329
1573
bus, do_not_queue=True)
2330
old_bus_name = (dbus.service.BusName
2331
("se.bsnet.fukt.Mandos", bus,
2333
except dbus.exceptions.NameExistsException as e:
2334
logger.error("Disabling D-Bus:", exc_info=e)
1574
except dbus.exceptions.NameExistsException, e:
1575
logger.error(unicode(e) + u", disabling D-Bus")
2335
1576
use_dbus = False
2336
server_settings["use_dbus"] = False
1577
server_settings[u"use_dbus"] = False
2337
1578
tcp_server.use_dbus = False
2338
1579
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2339
service = AvahiServiceToSyslog(name =
2340
server_settings["servicename"],
2341
servicetype = "_mandos._tcp",
2342
protocol = protocol, bus = bus)
1580
service = AvahiService(name = server_settings[u"servicename"],
1581
servicetype = u"_mandos._tcp",
1582
protocol = protocol, bus = bus)
2343
1583
if server_settings["interface"]:
2344
1584
service.interface = (if_nametoindex
2345
(str(server_settings["interface"])))
2347
global multiprocessing_manager
2348
multiprocessing_manager = multiprocessing.Manager()
1585
(str(server_settings[u"interface"])))
2350
1587
client_class = Client
2352
1589
client_class = functools.partial(ClientDBus, bus = bus)
2354
client_settings = Client.config_parser(client_config)
2355
old_client_settings = {}
2358
# Get client data and settings from last running state.
2359
if server_settings["restore"]:
2361
with open(stored_state_path, "rb") as stored_state:
2362
clients_data, old_client_settings = (pickle.load
2364
os.remove(stored_state_path)
2365
except IOError as e:
2366
if e.errno == errno.ENOENT:
2367
logger.warning("Could not load persistent state: {0}"
2368
.format(os.strerror(e.errno)))
2370
logger.critical("Could not load persistent state:",
2373
except EOFError as e:
2374
logger.warning("Could not load persistent state: "
2375
"EOFError:", exc_info=e)
2377
with PGPEngine() as pgp:
2378
for client_name, client in clients_data.iteritems():
2379
# Decide which value to use after restoring saved state.
2380
# We have three different values: Old config file,
2381
# new config file, and saved state.
2382
# New config value takes precedence if it differs from old
2383
# config value, otherwise use saved state.
2384
for name, value in client_settings[client_name].items():
2386
# For each value in new config, check if it
2387
# differs from the old config value (Except for
2388
# the "secret" attribute)
2389
if (name != "secret" and
2390
value != old_client_settings[client_name]
2392
client[name] = value
2396
# Clients who has passed its expire date can still be
2397
# enabled if its last checker was successful. Clients
2398
# whose checker succeeded before we stored its state is
2399
# assumed to have successfully run all checkers during
2401
if client["enabled"]:
2402
if datetime.datetime.utcnow() >= client["expires"]:
2403
if not client["last_checked_ok"]:
2405
"disabling client {0} - Client never "
2406
"performed a successful checker"
2407
.format(client_name))
2408
client["enabled"] = False
2409
elif client["last_checker_status"] != 0:
2411
"disabling client {0} - Client "
2412
"last checker failed with error code {1}"
2413
.format(client_name,
2414
client["last_checker_status"]))
2415
client["enabled"] = False
2417
client["expires"] = (datetime.datetime
2419
+ client["timeout"])
2420
logger.debug("Last checker succeeded,"
2421
" keeping {0} enabled"
2422
.format(client_name))
2424
client["secret"] = (
2425
pgp.decrypt(client["encrypted_secret"],
2426
client_settings[client_name]
2429
# If decryption fails, we use secret from new settings
2430
logger.debug("Failed to decrypt {0} old secret"
2431
.format(client_name))
2432
client["secret"] = (
2433
client_settings[client_name]["secret"])
2435
# Add/remove clients based on new changes made to config
2436
for client_name in (set(old_client_settings)
2437
- set(client_settings)):
2438
del clients_data[client_name]
2439
for client_name in (set(client_settings)
2440
- set(old_client_settings)):
2441
clients_data[client_name] = client_settings[client_name]
2443
# Create all client objects
2444
for client_name, client in clients_data.iteritems():
2445
tcp_server.clients[client_name] = client_class(
2446
name = client_name, settings = client)
1590
tcp_server.clients.update(set(
1591
client_class(name = section,
1592
config= dict(client_config.items(section)))
1593
for section in client_config.sections()))
2448
1594
if not tcp_server.clients:
2449
logger.warning("No clients defined")
1595
logger.warning(u"No clients defined")
1598
# Redirect stdin so all checkers get /dev/null
1599
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1600
os.dup2(null, sys.stdin.fileno())
1604
# No console logging
1605
logger.removeHandler(console)
1606
# Close all input and output, do double fork, etc.
1610
with closing(pidfile):
1612
pidfile.write(str(pid) + "\n")
1615
logger.error(u"Could not write to file %r with PID %d",
1618
# "pidfile" was never created
2455
pidfile.write(str(pid) + "\n".encode("utf-8"))
2458
logger.error("Could not write to file %r with PID %d",
2461
# "pidfile" was never created
2464
1623
signal.signal(signal.SIGINT, signal.SIG_IGN)
2466
1624
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2467
1625
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2470
@alternate_dbus_interfaces({"se.recompile.Mandos":
2471
"se.bsnet.fukt.Mandos"})
2472
class MandosDBusService(DBusObjectWithProperties):
1628
class MandosDBusService(dbus.service.Object):
2473
1629
"""A D-Bus proxy object"""
2474
1630
def __init__(self):
2475
dbus.service.Object.__init__(self, bus, "/")
2476
_interface = "se.recompile.Mandos"
2478
@dbus_interface_annotations(_interface)
2480
return { "org.freedesktop.DBus.Property"
2481
".EmitsChangedSignal":
2484
@dbus.service.signal(_interface, signature="o")
2485
def ClientAdded(self, objpath):
1631
dbus.service.Object.__init__(self, bus, u"/")
1632
_interface = u"se.bsnet.fukt.Mandos"
1634
@dbus.service.signal(_interface, signature=u"oa{sv}")
1635
def ClientAdded(self, objpath, properties):
2489
@dbus.service.signal(_interface, signature="ss")
1639
@dbus.service.signal(_interface, signature=u"ss")
2490
1640
def ClientNotFound(self, fingerprint, address):
2494
@dbus.service.signal(_interface, signature="os")
1644
@dbus.service.signal(_interface, signature=u"os")
2495
1645
def ClientRemoved(self, objpath, name):
2499
@dbus.service.method(_interface, out_signature="ao")
1649
@dbus.service.method(_interface, out_signature=u"ao")
2500
1650
def GetAllClients(self):
2502
1652
return dbus.Array(c.dbus_object_path
2504
tcp_server.clients.itervalues())
1653
for c in tcp_server.clients)
2506
1655
@dbus.service.method(_interface,
2507
out_signature="a{oa{sv}}")
1656
out_signature=u"a{oa{sv}}")
2508
1657
def GetAllClientsWithProperties(self):
2510
1659
return dbus.Dictionary(
2511
((c.dbus_object_path, c.GetAll(""))
2512
for c in tcp_server.clients.itervalues()),
1660
((c.dbus_object_path, c.GetAll(u""))
1661
for c in tcp_server.clients),
1662
signature=u"oa{sv}")
2515
@dbus.service.method(_interface, in_signature="o")
1664
@dbus.service.method(_interface, in_signature=u"o")
2516
1665
def RemoveClient(self, object_path):
2518
for c in tcp_server.clients.itervalues():
1667
for c in tcp_server.clients:
2519
1668
if c.dbus_object_path == object_path:
2520
del tcp_server.clients[c.name]
1669
tcp_server.clients.remove(c)
2521
1670
c.remove_from_connection()
2522
1671
# Don't signal anything except ClientRemoved
2523
1672
c.disable(quiet=True)