78
86
except ImportError:
79
87
SO_BINDTODEVICE = None
84
logger = logging.Logger(u'mandos')
90
stored_state_file = "clients.pickle"
92
logger = logging.getLogger()
85
93
syslogger = (logging.handlers.SysLogHandler
86
94
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
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)
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
99
213
class AvahiError(Exception):
100
214
def __init__(self, value, *args, **kwargs):
191
312
self.group.Commit()
192
313
def entry_group_state_changed(self, state, error):
193
314
"""Derived from the Avahi example code"""
194
logger.debug(u"Avahi state change: %i", state)
315
logger.debug("Avahi entry group state change: %i", state)
196
317
if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
logger.debug(u"Zeroconf service established.")
318
logger.debug("Zeroconf service established.")
198
319
elif state == avahi.ENTRY_GROUP_COLLISION:
199
logger.warning(u"Zeroconf service name collision.")
320
logger.info("Zeroconf service name collision.")
201
322
elif state == avahi.ENTRY_GROUP_FAILURE:
202
logger.critical(u"Avahi: Error in group state changed %s",
323
logger.critical("Avahi: Error in group state changed %s",
204
raise AvahiGroupError(u"State changed: %s"
325
raise AvahiGroupError("State changed: %s"
205
326
% unicode(error))
206
327
def cleanup(self):
207
328
"""Derived from the Avahi example code"""
208
329
if self.group is not None:
332
except (dbus.exceptions.UnknownMethodException,
333
dbus.exceptions.DBusException):
210
335
self.group = None
211
def server_state_changed(self, state):
337
def server_state_changed(self, state, error=None):
212
338
"""Derived from the Avahi example code"""
213
if state == avahi.SERVER_COLLISION:
214
logger.error(u"Zeroconf server name collision")
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)
216
354
elif state == avahi.SERVER_RUNNING:
358
logger.debug("Unknown state: %r", state)
360
logger.debug("Unknown state: %r: %r", state, error)
218
361
def activate(self):
219
362
"""Derived from the Avahi example code"""
220
363
if self.server is None:
221
364
self.server = dbus.Interface(
222
365
self.bus.get_object(avahi.DBUS_NAME,
223
avahi.DBUS_PATH_SERVER),
366
avahi.DBUS_PATH_SERVER,
367
follow_name_owner_changes=True),
224
368
avahi.DBUS_INTERFACE_SERVER)
225
self.server.connect_to_signal(u"StateChanged",
369
self.server.connect_to_signal("StateChanged",
226
370
self.server_state_changed)
227
371
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))
230
389
class Client(object):
231
390
"""A representation of a client host served by this server.
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)
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
248
396
checker: subprocess.Popen(); a running checker process used
249
397
to see if the client lives.
250
398
'None' if no process is running.
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
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
256
402
runtime with vars(self) as dict, so that for
257
403
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
258
408
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
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))
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",
268
447
def timeout_milliseconds(self):
269
448
"Return the 'timeout' attribute in milliseconds"
270
return self._timedelta_to_milliseconds(self.timeout)
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)
272
455
def interval_milliseconds(self):
273
456
"Return the 'interval' attribute in milliseconds"
274
return self._timedelta_to_milliseconds(self.interval)
457
return timedelta_to_milliseconds(self.interval)
276
def __init__(self, name = None, disable_hook=None, config=None):
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):
277
511
"""Note: the 'checker' key in 'config' sets the
278
512
'checker_command' attribute and *not* the 'checker'
283
logger.debug(u"Creating client %r", self.name)
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)
284
530
# Uppercase and remove spaces from fingerprint for later
285
531
# comparison purposes with return value from the fingerprint()
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
533
logger.debug(" Fingerprint: %s", self.fingerprint)
534
self.created = settings.get("created",
535
datetime.datetime.utcnow())
537
# attributes specific for this server instance
309
538
self.checker = None
310
539
self.checker_initiator_tag = None
311
540
self.disable_initiator_tag = None
312
541
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
314
542
self.current_checker_command = None
315
self.last_connect = None
544
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
561
def send_changedstate(self):
562
with self.changedstate:
563
self.changedstate.notify_all()
317
565
def enable(self):
318
566
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
567
if getattr(self, "enabled", False):
320
568
# Already enabled
570
self.send_changedstate()
571
self.expires = datetime.datetime.utcnow() + self.timeout
322
573
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):
323
600
# Schedule a new checker to be started an 'interval' from now,
324
601
# and every interval from then on.
325
602
self.checker_initiator_tag = (gobject.timeout_add
326
603
(self.interval_milliseconds(),
327
604
self.start_checker))
328
# Also start a new checker *right now*.
330
605
# Schedule a disable() when 'timeout' has passed
331
606
self.disable_initiator_tag = (gobject.timeout_add
332
607
(self.timeout_milliseconds(),
337
"""Disable this client."""
338
if not getattr(self, "enabled", False):
340
logger.info(u"Disabling client %s", self.name)
341
if getattr(self, u"disable_initiator_tag", False):
342
gobject.source_remove(self.disable_initiator_tag)
343
self.disable_initiator_tag = None
344
if getattr(self, u"checker_initiator_tag", False):
345
gobject.source_remove(self.checker_initiator_tag)
346
self.checker_initiator_tag = None
348
if self.disable_hook:
349
self.disable_hook(self)
351
# Do not run this again if called by a gobject.timeout_add
355
self.disable_hook = None
609
# Also start a new checker *right now*.
358
612
def checker_callback(self, pid, condition, command):
359
613
"""The checker has completed, so take appropriate actions."""
360
614
self.checker_callback_tag = None
361
615
self.checker = None
362
616
if os.WIFEXITED(condition):
363
exitstatus = os.WEXITSTATUS(condition)
365
logger.info(u"Checker for %(name)s succeeded",
617
self.last_checker_status = os.WEXITSTATUS(condition)
618
if self.last_checker_status == 0:
619
logger.info("Checker for %(name)s succeeded",
367
621
self.checked_ok()
369
logger.info(u"Checker for %(name)s failed",
623
logger.info("Checker for %(name)s failed",
372
logger.warning(u"Checker for %(name)s crashed?",
626
self.last_checker_status = -1
627
logger.warning("Checker for %(name)s crashed?",
375
630
def checked_ok(self):
376
"""Bump up the timeout for this client.
378
This should only be called when the client has been seen,
631
"""Assert that the client has been seen, alive and well."""
381
632
self.last_checked_ok = datetime.datetime.utcnow()
382
gobject.source_remove(self.disable_initiator_tag)
383
self.disable_initiator_tag = (gobject.timeout_add
384
(self.timeout_milliseconds(),
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()
387
651
def start_checker(self):
388
652
"""Start a new checker subprocess if one is not running.
533
831
class DBusObjectWithProperties(dbus.service.Object):
534
832
"""A D-Bus object with properties.
536
834
Classes inheriting from this can use the dbus_service_property
537
835
decorator to expose methods as D-Bus properties. It exposes the
538
836
standard Get(), Set(), and GetAll() methods on the D-Bus.
542
def _is_dbus_property(obj):
543
return getattr(obj, u"_dbus_is_property", False)
840
def _is_dbus_thing(thing):
841
"""Returns a function testing if an attribute is a D-Bus thing
843
If called like _is_dbus_thing("method") it returns a function
844
suitable for use as predicate to inspect.getmembers().
846
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
545
def _get_all_dbus_properties(self):
849
def _get_all_dbus_things(self, thing):
546
850
"""Returns a generator of (name, attribute) pairs
548
return ((prop._dbus_name, prop)
550
inspect.getmembers(self, self._is_dbus_property))
852
return ((getattr(athing.__get__(self), "_dbus_name",
854
athing.__get__(self))
855
for cls in self.__class__.__mro__
857
inspect.getmembers(cls,
858
self._is_dbus_thing(thing)))
552
860
def _get_dbus_property(self, interface_name, property_name):
553
861
"""Returns a bound method if one exists which is a D-Bus
554
862
property with the specified name and interface.
556
for name in (property_name,
557
property_name + u"_dbus_property"):
558
prop = getattr(self, name, None)
560
or not self._is_dbus_property(prop)
561
or prop._dbus_name != property_name
562
or (interface_name and prop._dbus_interface
563
and interface_name != prop._dbus_interface)):
864
for cls in self.__class__.__mro__:
865
for name, value in (inspect.getmembers
867
self._is_dbus_thing("property"))):
868
if (value._dbus_name == property_name
869
and value._dbus_interface == interface_name):
870
return value.__get__(self)
566
872
# No such property
567
raise DBusPropertyNotFound(self.dbus_object_path + u":"
568
+ interface_name + u"."
873
raise DBusPropertyNotFound(self.dbus_object_path + ":"
874
+ interface_name + "."
571
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
877
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
573
879
def Get(self, interface_name, property_name):
574
880
"""Standard D-Bus property Get() method, see D-Bus standard.
576
882
prop = self._get_dbus_property(interface_name, property_name)
577
if prop._dbus_access == u"write":
883
if prop._dbus_access == "write":
578
884
raise DBusPropertyAccessException(property_name)
580
if not hasattr(value, u"variant_level"):
886
if not hasattr(value, "variant_level"):
582
888
return type(value)(value, variant_level=value.variant_level+1)
584
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
890
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
585
891
def Set(self, interface_name, property_name, value):
586
892
"""Standard D-Bus property Set() method, see D-Bus standard.
588
894
prop = self._get_dbus_property(interface_name, property_name)
589
if prop._dbus_access == u"read":
895
if prop._dbus_access == "read":
590
896
raise DBusPropertyAccessException(property_name)
591
if prop._dbus_get_args_options[u"byte_arrays"]:
592
value = dbus.ByteArray(''.join(unichr(byte)
897
if prop._dbus_get_args_options["byte_arrays"]:
898
# The byte_arrays option is not supported yet on
899
# signatures other than "ay".
900
if prop._dbus_signature != "ay":
902
value = dbus.ByteArray(b''.join(chr(byte)
596
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
out_signature=u"a{sv}")
906
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
907
out_signature="a{sv}")
598
908
def GetAll(self, interface_name):
599
909
"""Standard D-Bus property GetAll() method, see D-Bus
602
912
Note: Will not include properties with access="write".
605
for name, prop in self._get_all_dbus_properties():
915
for name, prop in self._get_all_dbus_things("property"):
606
916
if (interface_name
607
917
and interface_name != prop._dbus_interface):
608
918
# Interface non-empty but did not match
610
920
# Ignore write-only properties
611
if prop._dbus_access == u"write":
921
if prop._dbus_access == "write":
614
if not hasattr(value, u"variant_level"):
924
if not hasattr(value, "variant_level"):
925
properties[name] = value
617
all[name] = type(value)(value, variant_level=
618
value.variant_level+1)
619
return dbus.Dictionary(all, signature=u"sv")
927
properties[name] = type(value)(value, variant_level=
928
value.variant_level+1)
929
return dbus.Dictionary(properties, signature="sv")
621
931
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
623
933
path_keyword='object_path',
624
934
connection_keyword='connection')
625
935
def Introspect(self, object_path, connection):
626
"""Standard D-Bus method, overloaded to insert property tags.
936
"""Overloading of standard D-Bus method.
938
Inserts property tags and interface annotation tags.
628
940
xmlstring = dbus.service.Object.Introspect(self, object_path,
631
943
document = xml.dom.minidom.parseString(xmlstring)
632
944
def make_tag(document, name, prop):
633
e = document.createElement(u"property")
634
e.setAttribute(u"name", name)
635
e.setAttribute(u"type", prop._dbus_signature)
636
e.setAttribute(u"access", prop._dbus_access)
945
e = document.createElement("property")
946
e.setAttribute("name", name)
947
e.setAttribute("type", prop._dbus_signature)
948
e.setAttribute("access", prop._dbus_access)
638
for if_tag in document.getElementsByTagName(u"interface"):
950
for if_tag in document.getElementsByTagName("interface"):
639
952
for tag in (make_tag(document, name, prop)
641
in self._get_all_dbus_properties()
954
in self._get_all_dbus_things("property")
642
955
if prop._dbus_interface
643
== if_tag.getAttribute(u"name")):
956
== if_tag.getAttribute("name")):
644
957
if_tag.appendChild(tag)
958
# Add annotation tags
959
for typ in ("method", "signal", "property"):
960
for tag in if_tag.getElementsByTagName(typ):
962
for name, prop in (self.
963
_get_all_dbus_things(typ)):
964
if (name == tag.getAttribute("name")
965
and prop._dbus_interface
966
== if_tag.getAttribute("name")):
967
annots.update(getattr
971
for name, value in annots.iteritems():
972
ann_tag = document.createElement(
974
ann_tag.setAttribute("name", name)
975
ann_tag.setAttribute("value", value)
976
tag.appendChild(ann_tag)
977
# Add interface annotation tags
978
for annotation, value in dict(
980
*(annotations().iteritems()
981
for name, annotations in
982
self._get_all_dbus_things("interface")
983
if name == if_tag.getAttribute("name")
985
ann_tag = document.createElement("annotation")
986
ann_tag.setAttribute("name", annotation)
987
ann_tag.setAttribute("value", value)
988
if_tag.appendChild(ann_tag)
645
989
# Add the names to the return values for the
646
990
# "org.freedesktop.DBus.Properties" methods
647
if (if_tag.getAttribute(u"name")
648
== u"org.freedesktop.DBus.Properties"):
649
for cn in if_tag.getElementsByTagName(u"method"):
650
if cn.getAttribute(u"name") == u"Get":
651
for arg in cn.getElementsByTagName(u"arg"):
652
if (arg.getAttribute(u"direction")
654
arg.setAttribute(u"name", u"value")
655
elif cn.getAttribute(u"name") == u"GetAll":
656
for arg in cn.getElementsByTagName(u"arg"):
657
if (arg.getAttribute(u"direction")
659
arg.setAttribute(u"name", u"props")
660
xmlstring = document.toxml(u"utf-8")
991
if (if_tag.getAttribute("name")
992
== "org.freedesktop.DBus.Properties"):
993
for cn in if_tag.getElementsByTagName("method"):
994
if cn.getAttribute("name") == "Get":
995
for arg in cn.getElementsByTagName("arg"):
996
if (arg.getAttribute("direction")
998
arg.setAttribute("name", "value")
999
elif cn.getAttribute("name") == "GetAll":
1000
for arg in cn.getElementsByTagName("arg"):
1001
if (arg.getAttribute("direction")
1003
arg.setAttribute("name", "props")
1004
xmlstring = document.toxml("utf-8")
661
1005
document.unlink()
662
1006
except (AttributeError, xml.dom.DOMException,
663
xml.parsers.expat.ExpatError), error:
664
logger.error(u"Failed to override Introspection method",
1007
xml.parsers.expat.ExpatError) as error:
1008
logger.error("Failed to override Introspection method",
666
1010
return xmlstring
1013
def datetime_to_dbus (dt, variant_level=0):
1014
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1016
return dbus.String("", variant_level = variant_level)
1017
return dbus.String(dt.isoformat(),
1018
variant_level=variant_level)
1021
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
1023
"""Applied to an empty subclass of a D-Bus object, this metaclass
1024
will add additional D-Bus attributes matching a certain pattern.
1026
def __new__(mcs, name, bases, attr):
1027
# Go through all the base classes which could have D-Bus
1028
# methods, signals, or properties in them
1029
old_interface_names = []
1030
for base in (b for b in bases
1031
if issubclass(b, dbus.service.Object)):
1032
# Go though all attributes of the base class
1033
for attrname, attribute in inspect.getmembers(base):
1034
# Ignore non-D-Bus attributes, and D-Bus attributes
1035
# with the wrong interface name
1036
if (not hasattr(attribute, "_dbus_interface")
1037
or not attribute._dbus_interface
1038
.startswith("se.recompile.Mandos")):
1040
# Create an alternate D-Bus interface name based on
1042
alt_interface = (attribute._dbus_interface
1043
.replace("se.recompile.Mandos",
1044
"se.bsnet.fukt.Mandos"))
1045
if alt_interface != attribute._dbus_interface:
1046
old_interface_names.append(alt_interface)
1047
# Is this a D-Bus signal?
1048
if getattr(attribute, "_dbus_is_signal", False):
1049
# Extract the original non-method function by
1051
nonmethod_func = (dict(
1052
zip(attribute.func_code.co_freevars,
1053
attribute.__closure__))["func"]
1055
# Create a new, but exactly alike, function
1056
# object, and decorate it to be a new D-Bus signal
1057
# with the alternate D-Bus interface name
1058
new_function = (dbus.service.signal
1060
attribute._dbus_signature)
1061
(types.FunctionType(
1062
nonmethod_func.func_code,
1063
nonmethod_func.func_globals,
1064
nonmethod_func.func_name,
1065
nonmethod_func.func_defaults,
1066
nonmethod_func.func_closure)))
1067
# Copy annotations, if any
1069
new_function._dbus_annotations = (
1070
dict(attribute._dbus_annotations))
1071
except AttributeError:
1073
# Define a creator of a function to call both the
1074
# old and new functions, so both the old and new
1075
# signals gets sent when the function is called
1076
def fixscope(func1, func2):
1077
"""This function is a scope container to pass
1078
func1 and func2 to the "call_both" function
1079
outside of its arguments"""
1080
def call_both(*args, **kwargs):
1081
"""This function will emit two D-Bus
1082
signals by calling func1 and func2"""
1083
func1(*args, **kwargs)
1084
func2(*args, **kwargs)
1086
# Create the "call_both" function and add it to
1088
attr[attrname] = fixscope(attribute,
1090
# Is this a D-Bus method?
1091
elif getattr(attribute, "_dbus_is_method", False):
1092
# Create a new, but exactly alike, function
1093
# object. Decorate it to be a new D-Bus method
1094
# with the alternate D-Bus interface name. Add it
1096
attr[attrname] = (dbus.service.method
1098
attribute._dbus_in_signature,
1099
attribute._dbus_out_signature)
1101
(attribute.func_code,
1102
attribute.func_globals,
1103
attribute.func_name,
1104
attribute.func_defaults,
1105
attribute.func_closure)))
1106
# Copy annotations, if any
1108
attr[attrname]._dbus_annotations = (
1109
dict(attribute._dbus_annotations))
1110
except AttributeError:
1112
# Is this a D-Bus property?
1113
elif getattr(attribute, "_dbus_is_property", False):
1114
# Create a new, but exactly alike, function
1115
# object, and decorate it to be a new D-Bus
1116
# property with the alternate D-Bus interface
1117
# name. Add it to the class.
1118
attr[attrname] = (dbus_service_property
1120
attribute._dbus_signature,
1121
attribute._dbus_access,
1123
._dbus_get_args_options
1126
(attribute.func_code,
1127
attribute.func_globals,
1128
attribute.func_name,
1129
attribute.func_defaults,
1130
attribute.func_closure)))
1131
# Copy annotations, if any
1133
attr[attrname]._dbus_annotations = (
1134
dict(attribute._dbus_annotations))
1135
except AttributeError:
1137
# Is this a D-Bus interface?
1138
elif getattr(attribute, "_dbus_is_interface", False):
1139
# Create a new, but exactly alike, function
1140
# object. Decorate it to be a new D-Bus interface
1141
# with the alternate D-Bus interface name. Add it
1143
attr[attrname] = (dbus_interface_annotations
1146
(attribute.func_code,
1147
attribute.func_globals,
1148
attribute.func_name,
1149
attribute.func_defaults,
1150
attribute.func_closure)))
1151
# Deprecate all old interfaces
1152
basename="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1153
for old_interface_name in old_interface_names:
1154
@dbus_interface_annotations(old_interface_name)
1156
return { "org.freedesktop.DBus.Deprecated": "true" }
1157
# Find an unused name
1158
for aname in (basename.format(i) for i in
1160
if aname not in attr:
1163
return type.__new__(mcs, name, bases, attr)
669
1166
class ClientDBus(Client, DBusObjectWithProperties):
670
1167
"""A Client class using D-Bus
680
1181
Client.__init__(self, *args, **kwargs)
681
1182
# Only now, when this client is initialized, can it show up on
1184
client_object_name = unicode(self.name).translate(
1185
{ord("."): ord("_"),
1186
ord("-"): ord("_")})
683
1187
self.dbus_object_path = (dbus.ObjectPath
685
+ self.name.replace(u".", u"_")))
1188
("/clients/" + client_object_name))
686
1189
DBusObjectWithProperties.__init__(self, self.bus,
687
1190
self.dbus_object_path)
690
def _datetime_to_dbus(dt, variant_level=0):
691
"""Convert a UTC datetime.datetime() to a D-Bus type."""
692
return dbus.String(dt.isoformat(),
693
variant_level=variant_level)
696
oldstate = getattr(self, u"enabled", False)
697
r = Client.enable(self)
698
if oldstate != self.enabled:
700
self.PropertyChanged(dbus.String(u"enabled"),
701
dbus.Boolean(True, variant_level=1))
702
self.PropertyChanged(
703
dbus.String(u"last_enabled"),
704
self._datetime_to_dbus(self.last_enabled,
708
def disable(self, signal = True):
709
oldstate = getattr(self, u"enabled", False)
710
r = Client.disable(self)
711
if signal and oldstate != self.enabled:
713
self.PropertyChanged(dbus.String(u"enabled"),
714
dbus.Boolean(False, variant_level=1))
1192
def notifychangeproperty(transform_func,
1193
dbus_name, type_func=lambda x: x,
1195
""" Modify a variable so that it's a property which announces
1196
its changes to DBus.
1198
transform_fun: Function that takes a value and a variant_level
1199
and transforms it to a D-Bus type.
1200
dbus_name: D-Bus name of the variable
1201
type_func: Function that transform the value before sending it
1202
to the D-Bus. Default: no transform
1203
variant_level: D-Bus variant level. Default: 1
1205
attrname = "_{0}".format(dbus_name)
1206
def setter(self, value):
1207
if hasattr(self, "dbus_object_path"):
1208
if (not hasattr(self, attrname) or
1209
type_func(getattr(self, attrname, None))
1210
!= type_func(value)):
1211
dbus_value = transform_func(type_func(value),
1214
self.PropertyChanged(dbus.String(dbus_name),
1216
setattr(self, attrname, value)
1218
return property(lambda self: getattr(self, attrname), setter)
1221
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1222
approvals_pending = notifychangeproperty(dbus.Boolean,
1225
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1226
last_enabled = notifychangeproperty(datetime_to_dbus,
1228
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1229
type_func = lambda checker:
1230
checker is not None)
1231
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1233
last_checker_status = notifychangeproperty(dbus.Int16,
1234
"LastCheckerStatus")
1235
last_approval_request = notifychangeproperty(
1236
datetime_to_dbus, "LastApprovalRequest")
1237
approved_by_default = notifychangeproperty(dbus.Boolean,
1238
"ApprovedByDefault")
1239
approval_delay = notifychangeproperty(dbus.UInt64,
1242
timedelta_to_milliseconds)
1243
approval_duration = notifychangeproperty(
1244
dbus.UInt64, "ApprovalDuration",
1245
type_func = timedelta_to_milliseconds)
1246
host = notifychangeproperty(dbus.String, "Host")
1247
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1249
timedelta_to_milliseconds)
1250
extended_timeout = notifychangeproperty(
1251
dbus.UInt64, "ExtendedTimeout",
1252
type_func = timedelta_to_milliseconds)
1253
interval = notifychangeproperty(dbus.UInt64,
1256
timedelta_to_milliseconds)
1257
checker_command = notifychangeproperty(dbus.String, "Checker")
1259
del notifychangeproperty
717
1261
def __del__(self, *args, **kwargs):
719
1263
self.remove_from_connection()
720
1264
except LookupError:
722
if hasattr(DBusObjectWithProperties, u"__del__"):
1266
if hasattr(DBusObjectWithProperties, "__del__"):
723
1267
DBusObjectWithProperties.__del__(self, *args, **kwargs)
724
1268
Client.__del__(self, *args, **kwargs)
841
1398
def StopChecker(self):
842
1399
self.stop_checker()
845
@dbus_service_property(_interface, signature=u"s", access=u"read")
846
def name_dbus_property(self):
1403
# ApprovalPending - property
1404
@dbus_service_property(_interface, signature="b", access="read")
1405
def ApprovalPending_dbus_property(self):
1406
return dbus.Boolean(bool(self.approvals_pending))
1408
# ApprovedByDefault - property
1409
@dbus_service_property(_interface, signature="b",
1411
def ApprovedByDefault_dbus_property(self, value=None):
1412
if value is None: # get
1413
return dbus.Boolean(self.approved_by_default)
1414
self.approved_by_default = bool(value)
1416
# ApprovalDelay - property
1417
@dbus_service_property(_interface, signature="t",
1419
def ApprovalDelay_dbus_property(self, value=None):
1420
if value is None: # get
1421
return dbus.UInt64(self.approval_delay_milliseconds())
1422
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1424
# ApprovalDuration - property
1425
@dbus_service_property(_interface, signature="t",
1427
def ApprovalDuration_dbus_property(self, value=None):
1428
if value is None: # get
1429
return dbus.UInt64(timedelta_to_milliseconds(
1430
self.approval_duration))
1431
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1434
@dbus_service_property(_interface, signature="s", access="read")
1435
def Name_dbus_property(self):
847
1436
return dbus.String(self.name)
849
# fingerprint - property
850
@dbus_service_property(_interface, signature=u"s", access=u"read")
851
def fingerprint_dbus_property(self):
1438
# Fingerprint - property
1439
@dbus_service_property(_interface, signature="s", access="read")
1440
def Fingerprint_dbus_property(self):
852
1441
return dbus.String(self.fingerprint)
855
@dbus_service_property(_interface, signature=u"s",
857
def host_dbus_property(self, value=None):
1444
@dbus_service_property(_interface, signature="s",
1446
def Host_dbus_property(self, value=None):
858
1447
if value is None: # get
859
1448
return dbus.String(self.host)
862
self.PropertyChanged(dbus.String(u"host"),
863
dbus.String(value, variant_level=1))
866
@dbus_service_property(_interface, signature=u"s", access=u"read")
867
def created_dbus_property(self):
868
return dbus.String(self._datetime_to_dbus(self.created))
870
# last_enabled - property
871
@dbus_service_property(_interface, signature=u"s", access=u"read")
872
def last_enabled_dbus_property(self):
873
if self.last_enabled is None:
874
return dbus.String(u"")
875
return dbus.String(self._datetime_to_dbus(self.last_enabled))
878
@dbus_service_property(_interface, signature=u"b",
880
def enabled_dbus_property(self, value=None):
1449
self.host = unicode(value)
1451
# Created - property
1452
@dbus_service_property(_interface, signature="s", access="read")
1453
def Created_dbus_property(self):
1454
return datetime_to_dbus(self.created)
1456
# LastEnabled - property
1457
@dbus_service_property(_interface, signature="s", access="read")
1458
def LastEnabled_dbus_property(self):
1459
return datetime_to_dbus(self.last_enabled)
1461
# Enabled - property
1462
@dbus_service_property(_interface, signature="b",
1464
def Enabled_dbus_property(self, value=None):
881
1465
if value is None: # get
882
1466
return dbus.Boolean(self.enabled)
888
# last_checked_ok - property
889
@dbus_service_property(_interface, signature=u"s",
891
def last_checked_ok_dbus_property(self, value=None):
1472
# LastCheckedOK - property
1473
@dbus_service_property(_interface, signature="s",
1475
def LastCheckedOK_dbus_property(self, value=None):
892
1476
if value is not None:
893
1477
self.checked_ok()
895
if self.last_checked_ok is None:
896
return dbus.String(u"")
897
return dbus.String(self._datetime_to_dbus(self
901
@dbus_service_property(_interface, signature=u"t",
903
def timeout_dbus_property(self, value=None):
1479
return datetime_to_dbus(self.last_checked_ok)
1481
# LastCheckerStatus - property
1482
@dbus_service_property(_interface, signature="n",
1484
def LastCheckerStatus_dbus_property(self):
1485
return dbus.Int16(self.last_checker_status)
1487
# Expires - property
1488
@dbus_service_property(_interface, signature="s", access="read")
1489
def Expires_dbus_property(self):
1490
return datetime_to_dbus(self.expires)
1492
# LastApprovalRequest - property
1493
@dbus_service_property(_interface, signature="s", access="read")
1494
def LastApprovalRequest_dbus_property(self):
1495
return datetime_to_dbus(self.last_approval_request)
1497
# Timeout - property
1498
@dbus_service_property(_interface, signature="t",
1500
def Timeout_dbus_property(self, value=None):
904
1501
if value is None: # get
905
1502
return dbus.UInt64(self.timeout_milliseconds())
906
1503
self.timeout = datetime.timedelta(0, 0, 0, value)
908
self.PropertyChanged(dbus.String(u"timeout"),
909
dbus.UInt64(value, variant_level=1))
910
if getattr(self, u"disable_initiator_tag", None) is None:
912
1504
# Reschedule timeout
913
gobject.source_remove(self.disable_initiator_tag)
914
self.disable_initiator_tag = None
916
_timedelta_to_milliseconds((self
922
# The timeout has passed
925
self.disable_initiator_tag = (gobject.timeout_add
926
(time_to_die, self.disable))
928
# interval - property
929
@dbus_service_property(_interface, signature=u"t",
931
def interval_dbus_property(self, value=None):
1506
now = datetime.datetime.utcnow()
1507
time_to_die = timedelta_to_milliseconds(
1508
(self.last_checked_ok + self.timeout) - now)
1509
if time_to_die <= 0:
1510
# The timeout has passed
1513
self.expires = (now +
1514
datetime.timedelta(milliseconds =
1516
if (getattr(self, "disable_initiator_tag", None)
1519
gobject.source_remove(self.disable_initiator_tag)
1520
self.disable_initiator_tag = (gobject.timeout_add
1524
# ExtendedTimeout - property
1525
@dbus_service_property(_interface, signature="t",
1527
def ExtendedTimeout_dbus_property(self, value=None):
1528
if value is None: # get
1529
return dbus.UInt64(self.extended_timeout_milliseconds())
1530
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1532
# Interval - property
1533
@dbus_service_property(_interface, signature="t",
1535
def Interval_dbus_property(self, value=None):
932
1536
if value is None: # get
933
1537
return dbus.UInt64(self.interval_milliseconds())
934
1538
self.interval = datetime.timedelta(0, 0, 0, value)
936
self.PropertyChanged(dbus.String(u"interval"),
937
dbus.UInt64(value, variant_level=1))
938
if getattr(self, u"checker_initiator_tag", None) is None:
1539
if getattr(self, "checker_initiator_tag", None) is None:
940
# Reschedule checker run
941
gobject.source_remove(self.checker_initiator_tag)
942
self.checker_initiator_tag = (gobject.timeout_add
943
(value, self.start_checker))
944
self.start_checker() # Start one now, too
947
@dbus_service_property(_interface, signature=u"s",
949
def checker_dbus_property(self, value=None):
1542
# Reschedule checker run
1543
gobject.source_remove(self.checker_initiator_tag)
1544
self.checker_initiator_tag = (gobject.timeout_add
1545
(value, self.start_checker))
1546
self.start_checker() # Start one now, too
1548
# Checker - property
1549
@dbus_service_property(_interface, signature="s",
1551
def Checker_dbus_property(self, value=None):
950
1552
if value is None: # get
951
1553
return dbus.String(self.checker_command)
952
self.checker_command = value
954
self.PropertyChanged(dbus.String(u"checker"),
955
dbus.String(self.checker_command,
1554
self.checker_command = unicode(value)
958
# checker_running - property
959
@dbus_service_property(_interface, signature=u"b",
961
def checker_running_dbus_property(self, value=None):
1556
# CheckerRunning - property
1557
@dbus_service_property(_interface, signature="b",
1559
def CheckerRunning_dbus_property(self, value=None):
962
1560
if value is None: # get
963
1561
return dbus.Boolean(self.checker is not None)
987
1615
Note: This will run in its own forked process."""
989
1617
def handle(self):
990
logger.info(u"TCP connection from: %s",
991
unicode(self.client_address))
992
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
993
# Open IPC pipe to parent process
994
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1618
with contextlib.closing(self.server.child_pipe) as child_pipe:
1619
logger.info("TCP connection from: %s",
1620
unicode(self.client_address))
1621
logger.debug("Pipe FD: %d",
1622
self.server.child_pipe.fileno())
995
1624
session = (gnutls.connection
996
1625
.ClientSession(self.request,
997
1626
gnutls.connection
998
1627
.X509Credentials()))
1000
line = self.request.makefile().readline()
1001
logger.debug(u"Protocol version: %r", line)
1003
if int(line.strip().split()[0]) > 1:
1005
except (ValueError, IndexError, RuntimeError), error:
1006
logger.error(u"Unknown protocol version: %s", error)
1009
1629
# Note: gnutls.connection.X509Credentials is really a
1010
1630
# generic GnuTLS certificate credentials object so long as
1011
1631
# no X.509 keys are added to it. Therefore, we can use it
1012
1632
# here despite using OpenPGP certificates.
1014
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1015
# u"+AES-256-CBC", u"+SHA1",
1016
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1634
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1635
# "+AES-256-CBC", "+SHA1",
1636
# "+COMP-NULL", "+CTYPE-OPENPGP",
1018
1638
# Use a fallback default, since this MUST be set.
1019
1639
priority = self.server.gnutls_priority
1020
1640
if priority is None:
1021
priority = u"NORMAL"
1022
1642
(gnutls.library.functions
1023
1643
.gnutls_priority_set_direct(session._c_object,
1024
1644
priority, None))
1646
# Start communication using the Mandos protocol
1647
# Get protocol number
1648
line = self.request.makefile().readline()
1649
logger.debug("Protocol version: %r", line)
1651
if int(line.strip().split()[0]) > 1:
1653
except (ValueError, IndexError, RuntimeError) as error:
1654
logger.error("Unknown protocol version: %s", error)
1657
# Start GnuTLS connection
1027
1659
session.handshake()
1028
except gnutls.errors.GNUTLSError, error:
1029
logger.warning(u"Handshake failed: %s", error)
1660
except gnutls.errors.GNUTLSError as error:
1661
logger.warning("Handshake failed: %s", error)
1030
1662
# Do not run session.bye() here: the session is not
1031
1663
# established. Just abandon the request.
1033
logger.debug(u"Handshake succeeded")
1665
logger.debug("Handshake succeeded")
1667
approval_required = False
1035
fpr = self.fingerprint(self.peer_certificate(session))
1036
except (TypeError, gnutls.errors.GNUTLSError), error:
1037
logger.warning(u"Bad certificate: %s", error)
1040
logger.debug(u"Fingerprint: %s", fpr)
1670
fpr = self.fingerprint(self.peer_certificate
1673
gnutls.errors.GNUTLSError) as error:
1674
logger.warning("Bad certificate: %s", error)
1676
logger.debug("Fingerprint: %s", fpr)
1679
client = ProxyClient(child_pipe, fpr,
1680
self.client_address)
1684
if client.approval_delay:
1685
delay = client.approval_delay
1686
client.approvals_pending += 1
1687
approval_required = True
1690
if not client.enabled:
1691
logger.info("Client %s is disabled",
1693
if self.server.use_dbus:
1695
client.Rejected("Disabled")
1698
if client.approved or not client.approval_delay:
1699
#We are approved or approval is disabled
1701
elif client.approved is None:
1702
logger.info("Client %s needs approval",
1704
if self.server.use_dbus:
1706
client.NeedApproval(
1707
client.approval_delay_milliseconds(),
1708
client.approved_by_default)
1710
logger.warning("Client %s was not approved",
1712
if self.server.use_dbus:
1714
client.Rejected("Denied")
1717
#wait until timeout or approved
1718
time = datetime.datetime.now()
1719
client.changedstate.acquire()
1720
(client.changedstate.wait
1721
(float(client.timedelta_to_milliseconds(delay)
1723
client.changedstate.release()
1724
time2 = datetime.datetime.now()
1725
if (time2 - time) >= delay:
1726
if not client.approved_by_default:
1727
logger.warning("Client %s timed out while"
1728
" waiting for approval",
1730
if self.server.use_dbus:
1732
client.Rejected("Approval timed out")
1737
delay -= time2 - time
1740
while sent_size < len(client.secret):
1742
sent = session.send(client.secret[sent_size:])
1743
except gnutls.errors.GNUTLSError as error:
1744
logger.warning("gnutls send failed")
1746
logger.debug("Sent: %d, remaining: %d",
1747
sent, len(client.secret)
1748
- (sent_size + sent))
1751
logger.info("Sending secret to %s", client.name)
1752
# bump the timeout using extended_timeout
1753
client.bump_timeout(client.extended_timeout)
1754
if self.server.use_dbus:
1042
for c in self.server.clients:
1043
if c.fingerprint == fpr:
1047
ipc.write(u"NOTFOUND %s %s\n"
1048
% (fpr, unicode(self.client_address)))
1051
# Have to check if client.still_valid(), since it is
1052
# possible that the client timed out while establishing
1053
# the GnuTLS session.
1054
if not client.still_valid():
1055
ipc.write(u"INVALID %s\n" % client.name)
1058
ipc.write(u"SENDING %s\n" % client.name)
1060
while sent_size < len(client.secret):
1061
sent = session.send(client.secret[sent_size:])
1062
logger.debug(u"Sent: %d, remaining: %d",
1063
sent, len(client.secret)
1064
- (sent_size + sent))
1759
if approval_required:
1760
client.approvals_pending -= 1
1763
except gnutls.errors.GNUTLSError as error:
1764
logger.warning("GnuTLS bye failed")
1069
1767
def peer_certificate(session):
1256
1982
for cond, name in
1257
1983
condition_names.iteritems()
1258
1984
if cond & condition)
1259
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1262
# Turn the pipe file descriptor into a Python file object
1263
if source not in file_objects:
1264
file_objects[source] = os.fdopen(source, u"r", 1)
1266
# Read a line from the file object
1267
cmdline = file_objects[source].readline()
1268
if not cmdline: # Empty line means end of file
1269
# close the IPC pipe
1270
file_objects[source].close()
1271
del file_objects[source]
1273
# Stop calling this function
1276
logger.debug(u"IPC command: %r", cmdline)
1278
# Parse and act on command
1279
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1281
if cmd == u"NOTFOUND":
1282
logger.warning(u"Client not found for fingerprint: %s",
1286
mandos_dbus_service.ClientNotFound(args)
1287
elif cmd == u"INVALID":
1288
for client in self.clients:
1289
if client.name == args:
1290
logger.warning(u"Client %s is invalid", args)
1296
logger.error(u"Unknown client %s is invalid", args)
1297
elif cmd == u"SENDING":
1298
for client in self.clients:
1299
if client.name == args:
1300
logger.info(u"Sending secret to %s", client.name)
1307
logger.error(u"Sending secret to unknown client %s",
1310
logger.error(u"Unknown IPC command: %r", cmdline)
1312
# Keep calling this function
1985
# error, or the other end of multiprocessing.Pipe has closed
1986
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1987
# Wait for other process to exit
1991
# Read a request from the child
1992
request = parent_pipe.recv()
1993
command = request[0]
1995
if command == 'init':
1997
address = request[2]
1999
for c in self.clients.itervalues():
2000
if c.fingerprint == fpr:
2004
logger.info("Client not found for fingerprint: %s, ad"
2005
"dress: %s", fpr, address)
2008
mandos_dbus_service.ClientNotFound(fpr,
2010
parent_pipe.send(False)
2013
gobject.io_add_watch(parent_pipe.fileno(),
2014
gobject.IO_IN | gobject.IO_HUP,
2015
functools.partial(self.handle_ipc,
2021
parent_pipe.send(True)
2022
# remove the old hook in favor of the new above hook on
2025
if command == 'funcall':
2026
funcname = request[1]
2030
parent_pipe.send(('data', getattr(client_object,
2034
if command == 'getattr':
2035
attrname = request[1]
2036
if callable(client_object.__getattribute__(attrname)):
2037
parent_pipe.send(('function',))
2039
parent_pipe.send(('data', client_object
2040
.__getattribute__(attrname)))
2042
if command == 'setattr':
2043
attrname = request[1]
2045
setattr(client_object, attrname, value)
1316
2050
def string_to_delta(interval):
1317
2051
"""Parse a string and return a datetime.timedelta
1319
>>> string_to_delta(u'7d')
2053
>>> string_to_delta('7d')
1320
2054
datetime.timedelta(7)
1321
>>> string_to_delta(u'60s')
2055
>>> string_to_delta('60s')
1322
2056
datetime.timedelta(0, 60)
1323
>>> string_to_delta(u'60m')
2057
>>> string_to_delta('60m')
1324
2058
datetime.timedelta(0, 3600)
1325
>>> string_to_delta(u'24h')
2059
>>> string_to_delta('24h')
1326
2060
datetime.timedelta(1)
1327
>>> string_to_delta(u'1w')
2061
>>> string_to_delta('1w')
1328
2062
datetime.timedelta(7)
1329
>>> string_to_delta(u'5m 30s')
2063
>>> string_to_delta('5m 30s')
1330
2064
datetime.timedelta(0, 330)
1332
2066
timevalue = datetime.timedelta(0)
1406
2116
##################################################################
1407
2117
# Parsing of options, both command line and config file
1409
parser = optparse.OptionParser(version = "%%prog %s" % version)
1410
parser.add_option("-i", u"--interface", type=u"string",
1411
metavar="IF", help=u"Bind to interface IF")
1412
parser.add_option("-a", u"--address", type=u"string",
1413
help=u"Address to listen for requests on")
1414
parser.add_option("-p", u"--port", type=u"int",
1415
help=u"Port number to receive requests on")
1416
parser.add_option("--check", action=u"store_true",
1417
help=u"Run self-test")
1418
parser.add_option("--debug", action=u"store_true",
1419
help=u"Debug mode; run in foreground and log to"
1421
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1422
u" priority string (see GnuTLS documentation)")
1423
parser.add_option("--servicename", type=u"string",
1424
metavar=u"NAME", help=u"Zeroconf service name")
1425
parser.add_option("--configdir", type=u"string",
1426
default=u"/etc/mandos", metavar=u"DIR",
1427
help=u"Directory to search for configuration"
1429
parser.add_option("--no-dbus", action=u"store_false",
1430
dest=u"use_dbus", help=u"Do not provide D-Bus"
1431
u" system bus interface")
1432
parser.add_option("--no-ipv6", action=u"store_false",
1433
dest=u"use_ipv6", help=u"Do not use IPv6")
1434
options = parser.parse_args()[0]
2119
parser = argparse.ArgumentParser()
2120
parser.add_argument("-v", "--version", action="version",
2121
version = "%%(prog)s %s" % version,
2122
help="show version number and exit")
2123
parser.add_argument("-i", "--interface", metavar="IF",
2124
help="Bind to interface IF")
2125
parser.add_argument("-a", "--address",
2126
help="Address to listen for requests on")
2127
parser.add_argument("-p", "--port", type=int,
2128
help="Port number to receive requests on")
2129
parser.add_argument("--check", action="store_true",
2130
help="Run self-test")
2131
parser.add_argument("--debug", action="store_true",
2132
help="Debug mode; run in foreground and log"
2134
parser.add_argument("--debuglevel", metavar="LEVEL",
2135
help="Debug level for stdout output")
2136
parser.add_argument("--priority", help="GnuTLS"
2137
" priority string (see GnuTLS documentation)")
2138
parser.add_argument("--servicename",
2139
metavar="NAME", help="Zeroconf service name")
2140
parser.add_argument("--configdir",
2141
default="/etc/mandos", metavar="DIR",
2142
help="Directory to search for configuration"
2144
parser.add_argument("--no-dbus", action="store_false",
2145
dest="use_dbus", help="Do not provide D-Bus"
2146
" system bus interface")
2147
parser.add_argument("--no-ipv6", action="store_false",
2148
dest="use_ipv6", help="Do not use IPv6")
2149
parser.add_argument("--no-restore", action="store_false",
2150
dest="restore", help="Do not restore stored"
2152
parser.add_argument("--statedir", metavar="DIR",
2153
help="Directory to save/restore state in")
2155
options = parser.parse_args()
1436
2157
if options.check:
1484
2209
##################################################################
1486
2211
# For convenience
1487
debug = server_settings[u"debug"]
1488
use_dbus = server_settings[u"use_dbus"]
1489
use_ipv6 = server_settings[u"use_ipv6"]
1492
syslogger.setLevel(logging.WARNING)
1493
console.setLevel(logging.WARNING)
1495
if server_settings[u"servicename"] != u"Mandos":
2212
debug = server_settings["debug"]
2213
debuglevel = server_settings["debuglevel"]
2214
use_dbus = server_settings["use_dbus"]
2215
use_ipv6 = server_settings["use_ipv6"]
2216
stored_state_path = os.path.join(server_settings["statedir"],
2220
initlogger(debug, logging.DEBUG)
2225
level = getattr(logging, debuglevel.upper())
2226
initlogger(debug, level)
2228
if server_settings["servicename"] != "Mandos":
1496
2229
syslogger.setFormatter(logging.Formatter
1497
(u'Mandos (%s) [%%(process)d]:'
1498
u' %%(levelname)s: %%(message)s'
1499
% server_settings[u"servicename"]))
2230
('Mandos (%s) [%%(process)d]:'
2231
' %%(levelname)s: %%(message)s'
2232
% server_settings["servicename"]))
1501
2234
# Parse config file with clients
1502
client_defaults = { u"timeout": u"1h",
1504
u"checker": u"fping -q -- %%(host)s",
1507
client_config = configparser.SafeConfigParser(client_defaults)
1508
client_config.read(os.path.join(server_settings[u"configdir"],
2235
client_config = configparser.SafeConfigParser(Client
2237
client_config.read(os.path.join(server_settings["configdir"],
1511
2240
global mandos_dbus_service
1512
2241
mandos_dbus_service = None
1514
tcp_server = MandosServer((server_settings[u"address"],
1515
server_settings[u"port"]),
2243
tcp_server = MandosServer((server_settings["address"],
2244
server_settings["port"]),
1517
interface=server_settings[u"interface"],
2246
interface=(server_settings["interface"]
1518
2248
use_ipv6=use_ipv6,
1519
2249
gnutls_priority=
1520
server_settings[u"priority"],
2250
server_settings["priority"],
1521
2251
use_dbus=use_dbus)
1522
pidfilename = u"/var/run/mandos.pid"
1524
pidfile = open(pidfilename, u"w")
1526
logger.error(u"Could not open file %r", pidfilename)
2253
pidfilename = "/var/run/mandos.pid"
2255
pidfile = open(pidfilename, "w")
2257
logger.error("Could not open file %r", pidfilename)
1529
uid = pwd.getpwnam(u"_mandos").pw_uid
1530
gid = pwd.getpwnam(u"_mandos").pw_gid
2260
uid = pwd.getpwnam("_mandos").pw_uid
2261
gid = pwd.getpwnam("_mandos").pw_gid
1531
2262
except KeyError:
1533
uid = pwd.getpwnam(u"mandos").pw_uid
1534
gid = pwd.getpwnam(u"mandos").pw_gid
2264
uid = pwd.getpwnam("mandos").pw_uid
2265
gid = pwd.getpwnam("mandos").pw_gid
1535
2266
except KeyError:
1537
uid = pwd.getpwnam(u"nobody").pw_uid
1538
gid = pwd.getpwnam(u"nobody").pw_gid
2268
uid = pwd.getpwnam("nobody").pw_uid
2269
gid = pwd.getpwnam("nobody").pw_gid
1539
2270
except KeyError:
1545
except OSError, error:
2276
except OSError as error:
1546
2277
if error[0] != errno.EPERM:
1549
# Enable all possible GnuTLS debugging
2281
# Enable all possible GnuTLS debugging
1551
2283
# "Use a log level over 10 to enable all debugging options."
1552
2284
# - GnuTLS manual
1553
2285
gnutls.library.functions.gnutls_global_set_log_level(11)
1555
2287
@gnutls.library.types.gnutls_log_func
1556
2288
def debug_gnutls(level, string):
1557
logger.debug(u"GnuTLS: %s", string[:-1])
2289
logger.debug("GnuTLS: %s", string[:-1])
1559
2291
(gnutls.library.functions
1560
2292
.gnutls_global_set_log_function(debug_gnutls))
2294
# Redirect stdin so all checkers get /dev/null
2295
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2296
os.dup2(null, sys.stdin.fileno())
2300
# Need to fork before connecting to D-Bus
2302
# Close all input and output, do double fork, etc.
2305
gobject.threads_init()
1562
2307
global main_loop
1563
2308
# From the Avahi example code
1564
DBusGMainLoop(set_as_default=True )
2309
DBusGMainLoop(set_as_default=True)
1565
2310
main_loop = gobject.MainLoop()
1566
2311
bus = dbus.SystemBus()
1567
2312
# End of Avahi example code
1569
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
2315
bus_name = dbus.service.BusName("se.recompile.Mandos",
2316
bus, do_not_queue=True)
2317
old_bus_name = (dbus.service.BusName
2318
("se.bsnet.fukt.Mandos", bus,
2320
except dbus.exceptions.NameExistsException as e:
2321
logger.error(unicode(e) + ", disabling D-Bus")
2323
server_settings["use_dbus"] = False
2324
tcp_server.use_dbus = False
1570
2325
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1571
service = AvahiService(name = server_settings[u"servicename"],
1572
servicetype = u"_mandos._tcp",
1573
protocol = protocol, bus = bus)
2326
service = AvahiServiceToSyslog(name =
2327
server_settings["servicename"],
2328
servicetype = "_mandos._tcp",
2329
protocol = protocol, bus = bus)
1574
2330
if server_settings["interface"]:
1575
2331
service.interface = (if_nametoindex
1576
(str(server_settings[u"interface"])))
2332
(str(server_settings["interface"])))
2334
global multiprocessing_manager
2335
multiprocessing_manager = multiprocessing.Manager()
1578
2337
client_class = Client
1580
client_class = functools.partial(ClientDBus, bus = bus)
1581
tcp_server.clients.update(set(
1582
client_class(name = section,
1583
config= dict(client_config.items(section)))
1584
for section in client_config.sections()))
2339
client_class = functools.partial(ClientDBusTransitional,
2342
client_settings = Client.config_parser(client_config)
2343
old_client_settings = {}
2346
# Get client data and settings from last running state.
2347
if server_settings["restore"]:
2349
with open(stored_state_path, "rb") as stored_state:
2350
clients_data, old_client_settings = (pickle.load
2352
os.remove(stored_state_path)
2353
except IOError as e:
2354
logger.warning("Could not load persistent state: {0}"
2356
if e.errno != errno.ENOENT:
2358
except EOFError as e:
2359
logger.warning("Could not load persistent state: "
2360
"EOFError: {0}".format(e))
2362
with PGPEngine() as pgp:
2363
for client_name, client in clients_data.iteritems():
2364
# Decide which value to use after restoring saved state.
2365
# We have three different values: Old config file,
2366
# new config file, and saved state.
2367
# New config value takes precedence if it differs from old
2368
# config value, otherwise use saved state.
2369
for name, value in client_settings[client_name].items():
2371
# For each value in new config, check if it
2372
# differs from the old config value (Except for
2373
# the "secret" attribute)
2374
if (name != "secret" and
2375
value != old_client_settings[client_name]
2377
client[name] = value
2381
# Clients who has passed its expire date can still be
2382
# enabled if its last checker was successful. Clients
2383
# whose checker succeeded before we stored its state is
2384
# assumed to have successfully run all checkers during
2386
if client["enabled"]:
2387
if datetime.datetime.utcnow() >= client["expires"]:
2388
if not client["last_checked_ok"]:
2390
"disabling client {0} - Client never "
2391
"performed a successful checker"
2392
.format(client_name))
2393
client["enabled"] = False
2394
elif client["last_checker_status"] != 0:
2396
"disabling client {0} - Client "
2397
"last checker failed with error code {1}"
2398
.format(client_name,
2399
client["last_checker_status"]))
2400
client["enabled"] = False
2402
client["expires"] = (datetime.datetime
2404
+ client["timeout"])
2405
logger.debug("Last checker succeeded,"
2406
" keeping {0} enabled"
2407
.format(client_name))
2409
client["secret"] = (
2410
pgp.decrypt(client["encrypted_secret"],
2411
client_settings[client_name]
2414
# If decryption fails, we use secret from new settings
2415
logger.debug("Failed to decrypt {0} old secret"
2416
.format(client_name))
2417
client["secret"] = (
2418
client_settings[client_name]["secret"])
2421
# Add/remove clients based on new changes made to config
2422
for client_name in (set(old_client_settings)
2423
- set(client_settings)):
2424
del clients_data[client_name]
2425
for client_name in (set(client_settings)
2426
- set(old_client_settings)):
2427
clients_data[client_name] = client_settings[client_name]
2429
# Create all client objects
2430
for client_name, client in clients_data.iteritems():
2431
tcp_server.clients[client_name] = client_class(
2432
name = client_name, settings = client)
1585
2434
if not tcp_server.clients:
1586
logger.warning(u"No clients defined")
1589
# Redirect stdin so all checkers get /dev/null
1590
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1591
os.dup2(null, sys.stdin.fileno())
1595
# No console logging
1596
logger.removeHandler(console)
1597
# Close all input and output, do double fork, etc.
1601
with closing(pidfile):
1603
pidfile.write(str(pid) + "\n")
1606
logger.error(u"Could not write to file %r with PID %d",
1609
# "pidfile" was never created
1614
"Cleanup function; run on exit"
2435
logger.warning("No clients defined")
1617
while tcp_server.clients:
1618
client = tcp_server.clients.pop()
1619
client.disable_hook = None
1622
atexit.register(cleanup)
2441
pidfile.write(str(pid) + "\n".encode("utf-8"))
2444
logger.error("Could not write to file %r with PID %d",
2447
# "pidfile" was never created
1625
2450
signal.signal(signal.SIGINT, signal.SIG_IGN)
1626
2452
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1627
2453
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1630
class MandosDBusService(dbus.service.Object):
2456
class MandosDBusService(DBusObjectWithProperties):
1631
2457
"""A D-Bus proxy object"""
1632
2458
def __init__(self):
1633
dbus.service.Object.__init__(self, bus, u"/")
1634
_interface = u"se.bsnet.fukt.Mandos"
1636
@dbus.service.signal(_interface, signature=u"oa{sv}")
1637
def ClientAdded(self, objpath, properties):
1641
@dbus.service.signal(_interface, signature=u"s")
1642
def ClientNotFound(self, fingerprint):
1646
@dbus.service.signal(_interface, signature=u"os")
2459
dbus.service.Object.__init__(self, bus, "/")
2460
_interface = "se.recompile.Mandos"
2462
@dbus_interface_annotations(_interface)
2464
return { "org.freedesktop.DBus.Property"
2465
".EmitsChangedSignal":
2468
@dbus.service.signal(_interface, signature="o")
2469
def ClientAdded(self, objpath):
2473
@dbus.service.signal(_interface, signature="ss")
2474
def ClientNotFound(self, fingerprint, address):
2478
@dbus.service.signal(_interface, signature="os")
1647
2479
def ClientRemoved(self, objpath, name):
1651
@dbus.service.method(_interface, out_signature=u"ao")
2483
@dbus.service.method(_interface, out_signature="ao")
1652
2484
def GetAllClients(self):
1654
2486
return dbus.Array(c.dbus_object_path
1655
for c in tcp_server.clients)
2488
tcp_server.clients.itervalues())
1657
2490
@dbus.service.method(_interface,
1658
out_signature=u"a{oa{sv}}")
2491
out_signature="a{oa{sv}}")
1659
2492
def GetAllClientsWithProperties(self):
1661
2494
return dbus.Dictionary(
1662
((c.dbus_object_path, c.GetAll(u""))
1663
for c in tcp_server.clients),
1664
signature=u"oa{sv}")
2495
((c.dbus_object_path, c.GetAll(""))
2496
for c in tcp_server.clients.itervalues()),
1666
@dbus.service.method(_interface, in_signature=u"o")
2499
@dbus.service.method(_interface, in_signature="o")
1667
2500
def RemoveClient(self, object_path):
1669
for c in tcp_server.clients:
2502
for c in tcp_server.clients.itervalues():
1670
2503
if c.dbus_object_path == object_path:
1671
tcp_server.clients.remove(c)
2504
del tcp_server.clients[c.name]
1672
2505
c.remove_from_connection()
1673
2506
# Don't signal anything except ClientRemoved
1674
c.disable(signal=False)
2507
c.disable(quiet=True)
1675
2508
# Emit D-Bus signal
1676
2509
self.ClientRemoved(object_path, c.name)
2511
raise KeyError(object_path)
1682
mandos_dbus_service = MandosDBusService()
1684
for client in tcp_server.clients:
2515
class MandosDBusServiceTransitional(MandosDBusService):
2516
__metaclass__ = AlternateDBusNamesMetaclass
2517
mandos_dbus_service = MandosDBusServiceTransitional()
2520
"Cleanup function; run on exit"
2523
multiprocessing.active_children()
2524
if not (tcp_server.clients or client_settings):
2527
# Store client before exiting. Secrets are encrypted with key
2528
# based on what config file has. If config file is
2529
# removed/edited, old secret will thus be unrecovable.
2531
with PGPEngine() as pgp:
2532
for client in tcp_server.clients.itervalues():
2533
key = client_settings[client.name]["secret"]
2534
client.encrypted_secret = pgp.encrypt(client.secret,
2538
# A list of attributes that can not be pickled
2540
exclude = set(("bus", "changedstate", "secret",
2542
for name, typ in (inspect.getmembers
2543
(dbus.service.Object)):
2546
client_dict["encrypted_secret"] = (client
2548
for attr in client.client_structure:
2549
if attr not in exclude:
2550
client_dict[attr] = getattr(client, attr)
2552
clients[client.name] = client_dict
2553
del client_settings[client.name]["secret"]
2556
tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
2559
(stored_state_path))
2560
with os.fdopen(tempfd, "wb") as stored_state:
2561
pickle.dump((clients, client_settings), stored_state)
2562
os.rename(tempname, stored_state_path)
2563
except (IOError, OSError) as e:
2564
logger.warning("Could not save persistent state: {0}"
2571
if e.errno not in set((errno.ENOENT, errno.EACCES,
2575
# Delete all clients, and settings from config
2576
while tcp_server.clients:
2577
name, client = tcp_server.clients.popitem()
2579
client.remove_from_connection()
2580
# Don't signal anything except ClientRemoved
2581
client.disable(quiet=True)
2584
mandos_dbus_service.ClientRemoved(client
2587
client_settings.clear()
2589
atexit.register(cleanup)
2591
for client in tcp_server.clients.itervalues():
1686
2593
# Emit D-Bus signal
1687
mandos_dbus_service.ClientAdded(client.dbus_object_path,
2594
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2595
# Need to initiate checking of clients
2597
client.init_checker()
1691
2599
tcp_server.enable()
1692
2600
tcp_server.server_activate()