88
81
except ImportError:
89
82
SO_BINDTODEVICE = None
92
stored_state_file = "clients.pickle"
94
logger = logging.getLogger()
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 = (logging.handlers.SysLogHandler
119
logging.handlers.SysLogHandler.LOG_DAEMON,
120
address = str("/dev/log")))
121
syslogger.setFormatter(logging.Formatter
122
('Mandos [%(process)d]: %(levelname)s:'
124
logger.addHandler(syslogger)
127
console = logging.StreamHandler()
128
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
132
logger.addHandler(console)
133
logger.setLevel(level)
136
class PGPError(Exception):
137
"""Exception if encryption/decryption fails"""
141
class PGPEngine(object):
142
"""A simple class for OpenPGP symmetric encryption & decryption"""
144
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
145
self.gnupgargs = ['--batch',
146
'--home', self.tempdir,
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
encoded = b"mandos" + binascii.hexlify(password)
178
if len(encoded) > 2048:
179
# GnuPG can't handle long passwords, so encode differently
180
encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
181
.replace(b"\n", b"\\n")
182
.replace(b"\0", b"\\x00"))
185
def encrypt(self, data, password):
186
passphrase = self.password_encode(password)
187
with tempfile.NamedTemporaryFile(dir=self.tempdir
189
passfile.write(passphrase)
191
proc = subprocess.Popen(['gpg', '--symmetric',
195
stdin = subprocess.PIPE,
196
stdout = subprocess.PIPE,
197
stderr = subprocess.PIPE)
198
ciphertext, err = proc.communicate(input = data)
199
if proc.returncode != 0:
203
def decrypt(self, data, password):
204
passphrase = self.password_encode(password)
205
with tempfile.NamedTemporaryFile(dir = self.tempdir
207
passfile.write(passphrase)
209
proc = subprocess.Popen(['gpg', '--decrypt',
213
stdin = subprocess.PIPE,
214
stdout = subprocess.PIPE,
215
stderr = subprocess.PIPE)
216
decrypted_plaintext, err = proc.communicate(input
218
if proc.returncode != 0:
220
return decrypted_plaintext
87
#logger = logging.getLogger('mandos')
88
logger = logging.Logger('mandos')
89
syslogger = (logging.handlers.SysLogHandler
90
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
91
address = str("/dev/log")))
92
syslogger.setFormatter(logging.Formatter
93
('Mandos [%(process)d]: %(levelname)s:'
95
logger.addHandler(syslogger)
97
console = logging.StreamHandler()
98
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
101
logger.addHandler(console)
223
103
class AvahiError(Exception):
224
104
def __init__(self, value, *args, **kwargs):
435
297
interval: datetime.timedelta(); How often to start a new checker
436
298
last_approval_request: datetime.datetime(); (UTC) or None
437
299
last_checked_ok: datetime.datetime(); (UTC) or None
438
last_checker_status: integer between 0 and 255 reflecting exit
439
status of last checker. -1 reflects crashed
440
checker, -2 means no checker completed yet.
441
last_enabled: datetime.datetime(); (UTC) or None
300
last_enabled: datetime.datetime(); (UTC)
442
301
name: string; from the config file, used in log messages and
443
302
D-Bus identifiers
444
303
secret: bytestring; sent verbatim (over TLS) to client
445
304
timeout: datetime.timedelta(); How long from last_checked_ok
446
305
until this client is disabled
447
extended_timeout: extra long timeout when secret has been sent
306
extended_timeout: extra long timeout when password has been sent
448
307
runtime_expansions: Allowed attributes for runtime expansion.
449
308
expires: datetime.datetime(); time (UTC) when a client will be
450
309
disabled, or None
451
server_settings: The server_settings dict from main()
454
312
runtime_expansions = ("approval_delay", "approval_duration",
455
"created", "enabled", "expires",
456
"fingerprint", "host", "interval",
457
"last_approval_request", "last_checked_ok",
313
"created", "enabled", "fingerprint",
314
"host", "interval", "last_checked_ok",
458
315
"last_enabled", "name", "timeout")
459
client_defaults = { "timeout": "PT5M",
460
"extended_timeout": "PT15M",
462
"checker": "fping -q -- %%(host)s",
464
"approval_delay": "PT0S",
465
"approval_duration": "PT1S",
466
"approved_by_default": "True",
470
317
def timeout_milliseconds(self):
471
318
"Return the 'timeout' attribute in milliseconds"
472
return timedelta_to_milliseconds(self.timeout)
319
return _timedelta_to_milliseconds(self.timeout)
474
321
def extended_timeout_milliseconds(self):
475
322
"Return the 'extended_timeout' attribute in milliseconds"
476
return timedelta_to_milliseconds(self.extended_timeout)
323
return _timedelta_to_milliseconds(self.extended_timeout)
478
325
def interval_milliseconds(self):
479
326
"Return the 'interval' attribute in milliseconds"
480
return timedelta_to_milliseconds(self.interval)
327
return _timedelta_to_milliseconds(self.interval)
482
329
def approval_delay_milliseconds(self):
483
return timedelta_to_milliseconds(self.approval_delay)
486
def config_parser(config):
487
"""Construct a new dict of client settings of this form:
488
{ client_name: {setting_name: value, ...}, ...}
489
with exceptions for any special settings as defined above.
490
NOTE: Must be a pure function. Must return the same result
491
value given the same arguments.
494
for client_name in config.sections():
495
section = dict(config.items(client_name))
496
client = settings[client_name] = {}
498
client["host"] = section["host"]
499
# Reformat values from string types to Python types
500
client["approved_by_default"] = config.getboolean(
501
client_name, "approved_by_default")
502
client["enabled"] = config.getboolean(client_name,
505
client["fingerprint"] = (section["fingerprint"].upper()
507
if "secret" in section:
508
client["secret"] = section["secret"].decode("base64")
509
elif "secfile" in section:
510
with open(os.path.expanduser(os.path.expandvars
511
(section["secfile"])),
513
client["secret"] = secfile.read()
515
raise TypeError("No secret or secfile for section {0}"
517
client["timeout"] = string_to_delta(section["timeout"])
518
client["extended_timeout"] = string_to_delta(
519
section["extended_timeout"])
520
client["interval"] = string_to_delta(section["interval"])
521
client["approval_delay"] = string_to_delta(
522
section["approval_delay"])
523
client["approval_duration"] = string_to_delta(
524
section["approval_duration"])
525
client["checker_command"] = section["checker"]
526
client["last_approval_request"] = None
527
client["last_checked_ok"] = None
528
client["last_checker_status"] = -2
532
def __init__(self, settings, name = None, server_settings=None):
330
return _timedelta_to_milliseconds(self.approval_delay)
332
def __init__(self, name = None, disable_hook=None, config=None):
333
"""Note: the 'checker' key in 'config' sets the
334
'checker_command' attribute and *not* the 'checker'
534
if server_settings is None:
536
self.server_settings = server_settings
537
# adding all client settings
538
for setting, value in settings.iteritems():
539
setattr(self, setting, value)
542
if not hasattr(self, "last_enabled"):
543
self.last_enabled = datetime.datetime.utcnow()
544
if not hasattr(self, "expires"):
545
self.expires = (datetime.datetime.utcnow()
548
self.last_enabled = None
551
339
logger.debug("Creating client %r", self.name)
552
340
# Uppercase and remove spaces from fingerprint for later
553
341
# comparison purposes with return value from the fingerprint()
343
self.fingerprint = (config["fingerprint"].upper()
555
345
logger.debug(" Fingerprint: %s", self.fingerprint)
556
self.created = settings.get("created",
557
datetime.datetime.utcnow())
559
# attributes specific for this server instance
346
if "secret" in config:
347
self.secret = config["secret"].decode("base64")
348
elif "secfile" in config:
349
with open(os.path.expanduser(os.path.expandvars
350
(config["secfile"])),
352
self.secret = secfile.read()
354
raise TypeError("No secret or secfile for client %s"
356
self.host = config.get("host", "")
357
self.created = datetime.datetime.utcnow()
359
self.last_approval_request = None
360
self.last_enabled = None
361
self.last_checked_ok = None
362
self.timeout = string_to_delta(config["timeout"])
363
self.extended_timeout = string_to_delta(config["extended_timeout"])
364
self.interval = string_to_delta(config["interval"])
365
self.disable_hook = disable_hook
560
366
self.checker = None
561
367
self.checker_initiator_tag = None
562
368
self.disable_initiator_tag = None
563
370
self.checker_callback_tag = None
371
self.checker_command = config["checker"]
564
372
self.current_checker_command = None
373
self.last_connect = None
374
self._approved = None
375
self.approved_by_default = config.get("approved_by_default",
566
377
self.approvals_pending = 0
567
self.changedstate = (multiprocessing_manager
568
.Condition(multiprocessing_manager
570
self.client_structure = [attr for attr in
571
self.__dict__.iterkeys()
572
if not attr.startswith("_")]
573
self.client_structure.append("client_structure")
575
for name, t in inspect.getmembers(type(self),
579
if not name.startswith("_"):
580
self.client_structure.append(name)
378
self.approval_delay = string_to_delta(
379
config["approval_delay"])
380
self.approval_duration = string_to_delta(
381
config["approval_duration"])
382
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
582
# Send notice to process children that client state has changed
583
384
def send_changedstate(self):
584
with self.changedstate:
585
self.changedstate.notify_all()
385
self.changedstate.acquire()
386
self.changedstate.notify_all()
387
self.changedstate.release()
587
389
def enable(self):
588
390
"""Start this client's checker and timeout hooks"""
589
391
if getattr(self, "enabled", False):
590
392
# Already enabled
394
self.send_changedstate()
395
# Schedule a new checker to be started an 'interval' from now,
396
# and every interval from then on.
397
self.checker_initiator_tag = (gobject.timeout_add
398
(self.interval_milliseconds(),
400
# Schedule a disable() when 'timeout' has passed
592
401
self.expires = datetime.datetime.utcnow() + self.timeout
402
self.disable_initiator_tag = (gobject.timeout_add
403
(self.timeout_milliseconds(),
593
405
self.enabled = True
594
406
self.last_enabled = datetime.datetime.utcnow()
596
self.send_changedstate()
407
# Also start a new checker *right now*.
598
410
def disable(self, quiet=True):
599
411
"""Disable this client."""
600
412
if not getattr(self, "enabled", False):
415
self.send_changedstate()
603
417
logger.info("Disabling client %s", self.name)
604
if getattr(self, "disable_initiator_tag", None) is not None:
418
if getattr(self, "disable_initiator_tag", False):
605
419
gobject.source_remove(self.disable_initiator_tag)
606
420
self.disable_initiator_tag = None
607
421
self.expires = None
608
if getattr(self, "checker_initiator_tag", None) is not None:
422
if getattr(self, "checker_initiator_tag", False):
609
423
gobject.source_remove(self.checker_initiator_tag)
610
424
self.checker_initiator_tag = None
611
425
self.stop_checker()
426
if self.disable_hook:
427
self.disable_hook(self)
612
428
self.enabled = False
614
self.send_changedstate()
615
429
# Do not run this again if called by a gobject.timeout_add
618
432
def __del__(self):
433
self.disable_hook = None
621
def init_checker(self):
622
# Schedule a new checker to be started an 'interval' from now,
623
# and every interval from then on.
624
if self.checker_initiator_tag is not None:
625
gobject.source_remove(self.checker_initiator_tag)
626
self.checker_initiator_tag = (gobject.timeout_add
627
(self.interval_milliseconds(),
629
# Schedule a disable() when 'timeout' has passed
630
if self.disable_initiator_tag is not None:
631
gobject.source_remove(self.disable_initiator_tag)
632
self.disable_initiator_tag = (gobject.timeout_add
633
(self.timeout_milliseconds(),
635
# Also start a new checker *right now*.
638
436
def checker_callback(self, pid, condition, command):
639
437
"""The checker has completed, so take appropriate actions."""
640
438
self.checker_callback_tag = None
641
439
self.checker = None
642
440
if os.WIFEXITED(condition):
643
self.last_checker_status = os.WEXITSTATUS(condition)
644
if self.last_checker_status == 0:
441
exitstatus = os.WEXITSTATUS(condition)
645
443
logger.info("Checker for %(name)s succeeded",
647
445
self.checked_ok()
869
613
class DBusObjectWithProperties(dbus.service.Object):
870
614
"""A D-Bus object with properties.
872
616
Classes inheriting from this can use the dbus_service_property
873
617
decorator to expose methods as D-Bus properties. It exposes the
874
618
standard Get(), Set(), and GetAll() methods on the D-Bus.
878
def _is_dbus_thing(thing):
879
"""Returns a function testing if an attribute is a D-Bus thing
881
If called like _is_dbus_thing("method") it returns a function
882
suitable for use as predicate to inspect.getmembers().
884
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
622
def _is_dbus_property(obj):
623
return getattr(obj, "_dbus_is_property", False)
887
def _get_all_dbus_things(self, thing):
625
def _get_all_dbus_properties(self):
888
626
"""Returns a generator of (name, attribute) pairs
890
return ((getattr(athing.__get__(self), "_dbus_name",
892
athing.__get__(self))
893
for cls in self.__class__.__mro__
895
inspect.getmembers(cls,
896
self._is_dbus_thing(thing)))
628
return ((prop._dbus_name, prop)
630
inspect.getmembers(self, self._is_dbus_property))
898
632
def _get_dbus_property(self, interface_name, property_name):
899
633
"""Returns a bound method if one exists which is a D-Bus
900
634
property with the specified name and interface.
902
for cls in self.__class__.__mro__:
903
for name, value in (inspect.getmembers
905
self._is_dbus_thing("property"))):
906
if (value._dbus_name == property_name
907
and value._dbus_interface == interface_name):
908
return value.__get__(self)
636
for name in (property_name,
637
property_name + "_dbus_property"):
638
prop = getattr(self, name, None)
640
or not self._is_dbus_property(prop)
641
or prop._dbus_name != property_name
642
or (interface_name and prop._dbus_interface
643
and interface_name != prop._dbus_interface)):
910
646
# No such property
911
647
raise DBusPropertyNotFound(self.dbus_object_path + ":"
912
648
+ interface_name + "."
1046
746
except (AttributeError, xml.dom.DOMException,
1047
747
xml.parsers.expat.ExpatError) as error:
1048
748
logger.error("Failed to override Introspection method",
1050
750
return xmlstring
1053
def datetime_to_dbus(dt, variant_level=0):
753
def datetime_to_dbus (dt, variant_level=0):
1054
754
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1056
756
return dbus.String("", variant_level = variant_level)
1057
757
return dbus.String(dt.isoformat(),
1058
758
variant_level=variant_level)
1061
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1062
"""A class decorator; applied to a subclass of
1063
dbus.service.Object, it will add alternate D-Bus attributes with
1064
interface names according to the "alt_interface_names" mapping.
1067
@alternate_dbus_interfaces({"org.example.Interface":
1068
"net.example.AlternateInterface"})
1069
class SampleDBusObject(dbus.service.Object):
1070
@dbus.service.method("org.example.Interface")
1071
def SampleDBusMethod():
1074
The above "SampleDBusMethod" on "SampleDBusObject" will be
1075
reachable via two interfaces: "org.example.Interface" and
1076
"net.example.AlternateInterface", the latter of which will have
1077
its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
1078
"true", unless "deprecate" is passed with a False value.
1080
This works for methods and signals, and also for D-Bus properties
1081
(from DBusObjectWithProperties) and interfaces (from the
1082
dbus_interface_annotations decorator).
1085
for orig_interface_name, alt_interface_name in (
1086
alt_interface_names.iteritems()):
1088
interface_names = set()
1089
# Go though all attributes of the class
1090
for attrname, attribute in inspect.getmembers(cls):
1091
# Ignore non-D-Bus attributes, and D-Bus attributes
1092
# with the wrong interface name
1093
if (not hasattr(attribute, "_dbus_interface")
1094
or not attribute._dbus_interface
1095
.startswith(orig_interface_name)):
1097
# Create an alternate D-Bus interface name based on
1099
alt_interface = (attribute._dbus_interface
1100
.replace(orig_interface_name,
1101
alt_interface_name))
1102
interface_names.add(alt_interface)
1103
# Is this a D-Bus signal?
1104
if getattr(attribute, "_dbus_is_signal", False):
1105
# Extract the original non-method undecorated
1106
# function by black magic
1107
nonmethod_func = (dict(
1108
zip(attribute.func_code.co_freevars,
1109
attribute.__closure__))["func"]
1111
# Create a new, but exactly alike, function
1112
# object, and decorate it to be a new D-Bus signal
1113
# with the alternate D-Bus interface name
1114
new_function = (dbus.service.signal
1116
attribute._dbus_signature)
1117
(types.FunctionType(
1118
nonmethod_func.func_code,
1119
nonmethod_func.func_globals,
1120
nonmethod_func.func_name,
1121
nonmethod_func.func_defaults,
1122
nonmethod_func.func_closure)))
1123
# Copy annotations, if any
1125
new_function._dbus_annotations = (
1126
dict(attribute._dbus_annotations))
1127
except AttributeError:
1129
# Define a creator of a function to call both the
1130
# original and alternate functions, so both the
1131
# original and alternate signals gets sent when
1132
# the function is called
1133
def fixscope(func1, func2):
1134
"""This function is a scope container to pass
1135
func1 and func2 to the "call_both" function
1136
outside of its arguments"""
1137
def call_both(*args, **kwargs):
1138
"""This function will emit two D-Bus
1139
signals by calling func1 and func2"""
1140
func1(*args, **kwargs)
1141
func2(*args, **kwargs)
1143
# Create the "call_both" function and add it to
1145
attr[attrname] = fixscope(attribute, new_function)
1146
# Is this a D-Bus method?
1147
elif getattr(attribute, "_dbus_is_method", False):
1148
# Create a new, but exactly alike, function
1149
# object. Decorate it to be a new D-Bus method
1150
# with the alternate D-Bus interface name. Add it
1152
attr[attrname] = (dbus.service.method
1154
attribute._dbus_in_signature,
1155
attribute._dbus_out_signature)
1157
(attribute.func_code,
1158
attribute.func_globals,
1159
attribute.func_name,
1160
attribute.func_defaults,
1161
attribute.func_closure)))
1162
# Copy annotations, if any
1164
attr[attrname]._dbus_annotations = (
1165
dict(attribute._dbus_annotations))
1166
except AttributeError:
1168
# Is this a D-Bus property?
1169
elif getattr(attribute, "_dbus_is_property", False):
1170
# Create a new, but exactly alike, function
1171
# object, and decorate it to be a new D-Bus
1172
# property with the alternate D-Bus interface
1173
# name. Add it to the class.
1174
attr[attrname] = (dbus_service_property
1176
attribute._dbus_signature,
1177
attribute._dbus_access,
1179
._dbus_get_args_options
1182
(attribute.func_code,
1183
attribute.func_globals,
1184
attribute.func_name,
1185
attribute.func_defaults,
1186
attribute.func_closure)))
1187
# Copy annotations, if any
1189
attr[attrname]._dbus_annotations = (
1190
dict(attribute._dbus_annotations))
1191
except AttributeError:
1193
# Is this a D-Bus interface?
1194
elif getattr(attribute, "_dbus_is_interface", False):
1195
# Create a new, but exactly alike, function
1196
# object. Decorate it to be a new D-Bus interface
1197
# with the alternate D-Bus interface name. Add it
1199
attr[attrname] = (dbus_interface_annotations
1202
(attribute.func_code,
1203
attribute.func_globals,
1204
attribute.func_name,
1205
attribute.func_defaults,
1206
attribute.func_closure)))
1208
# Deprecate all alternate interfaces
1209
iname="_AlternateDBusNames_interface_annotation{0}"
1210
for interface_name in interface_names:
1211
@dbus_interface_annotations(interface_name)
1213
return { "org.freedesktop.DBus.Deprecated":
1215
# Find an unused name
1216
for aname in (iname.format(i)
1217
for i in itertools.count()):
1218
if aname not in attr:
1222
# Replace the class with a new subclass of it with
1223
# methods, signals, etc. as created above.
1224
cls = type(b"{0}Alternate".format(cls.__name__),
1230
@alternate_dbus_interfaces({"se.recompile.Mandos":
1231
"se.bsnet.fukt.Mandos"})
1232
760
class ClientDBus(Client, DBusObjectWithProperties):
1233
761
"""A Client class using D-Bus
1291
818
last_enabled = notifychangeproperty(datetime_to_dbus,
1293
820
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1294
type_func = lambda checker:
1295
checker is not None)
821
type_func = lambda checker: checker is not None)
1296
822
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1297
823
"LastCheckedOK")
1298
last_checker_status = notifychangeproperty(dbus.Int16,
1299
"LastCheckerStatus")
1300
last_approval_request = notifychangeproperty(
1301
datetime_to_dbus, "LastApprovalRequest")
824
last_approval_request = notifychangeproperty(datetime_to_dbus,
825
"LastApprovalRequest")
1302
826
approved_by_default = notifychangeproperty(dbus.Boolean,
1303
827
"ApprovedByDefault")
1304
approval_delay = notifychangeproperty(dbus.UInt64,
1307
timedelta_to_milliseconds)
1308
approval_duration = notifychangeproperty(
1309
dbus.UInt64, "ApprovalDuration",
1310
type_func = timedelta_to_milliseconds)
828
approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
829
type_func = _timedelta_to_milliseconds)
830
approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
831
type_func = _timedelta_to_milliseconds)
1311
832
host = notifychangeproperty(dbus.String, "Host")
1312
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1314
timedelta_to_milliseconds)
1315
extended_timeout = notifychangeproperty(
1316
dbus.UInt64, "ExtendedTimeout",
1317
type_func = timedelta_to_milliseconds)
1318
interval = notifychangeproperty(dbus.UInt64,
1321
timedelta_to_milliseconds)
833
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
834
type_func = _timedelta_to_milliseconds)
835
extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
836
type_func = _timedelta_to_milliseconds)
837
interval = notifychangeproperty(dbus.UInt16, "Interval",
838
type_func = _timedelta_to_milliseconds)
1322
839
checker_command = notifychangeproperty(dbus.String, "Checker")
1324
841
del notifychangeproperty
2030
1499
def __init__(self, server_address, RequestHandlerClass,
2031
1500
interface=None, use_ipv6=True, clients=None,
2032
gnutls_priority=None, use_dbus=True, socketfd=None):
1501
gnutls_priority=None, use_dbus=True):
2033
1502
self.enabled = False
2034
1503
self.clients = clients
2035
1504
if self.clients is None:
1505
self.clients = set()
2037
1506
self.use_dbus = use_dbus
2038
1507
self.gnutls_priority = gnutls_priority
2039
1508
IPv6_TCPServer.__init__(self, server_address,
2040
1509
RequestHandlerClass,
2041
1510
interface = interface,
2042
use_ipv6 = use_ipv6,
2043
socketfd = socketfd)
1511
use_ipv6 = use_ipv6)
2044
1512
def server_activate(self):
2045
1513
if self.enabled:
2046
1514
return socketserver.TCPServer.server_activate(self)
2048
1515
def enable(self):
2049
1516
self.enabled = True
2051
def add_pipe(self, parent_pipe, proc):
1517
def add_pipe(self, parent_pipe):
2052
1518
# Call "handle_ipc" for both data and EOF events
2053
1519
gobject.io_add_watch(parent_pipe.fileno(),
2054
1520
gobject.IO_IN | gobject.IO_HUP,
2055
1521
functools.partial(self.handle_ipc,
1522
parent_pipe = parent_pipe))
2060
1524
def handle_ipc(self, source, condition, parent_pipe=None,
2061
proc = None, client_object=None):
2062
# error, or the other end of multiprocessing.Pipe has closed
2063
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2064
# Wait for other process to exit
1525
client_object=None):
1527
gobject.IO_IN: "IN", # There is data to read.
1528
gobject.IO_OUT: "OUT", # Data can be written (without
1530
gobject.IO_PRI: "PRI", # There is urgent data to read.
1531
gobject.IO_ERR: "ERR", # Error condition.
1532
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1533
# broken, usually for pipes and
1536
conditions_string = ' | '.join(name
1538
condition_names.iteritems()
1539
if cond & condition)
1540
# error or the other end of multiprocessing.Pipe has closed
1541
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
2068
1544
# Read a request from the child
2082
1558
"dress: %s", fpr, address)
2083
1559
if self.use_dbus:
2084
1560
# Emit D-Bus signal
2085
mandos_dbus_service.ClientNotFound(fpr,
1561
mandos_dbus_service.ClientNotFound(fpr, address[0])
2087
1562
parent_pipe.send(False)
2090
1565
gobject.io_add_watch(parent_pipe.fileno(),
2091
1566
gobject.IO_IN | gobject.IO_HUP,
2092
1567
functools.partial(self.handle_ipc,
1568
parent_pipe = parent_pipe,
1569
client_object = client))
2098
1570
parent_pipe.send(True)
2099
# remove the old hook in favor of the new above hook on
1571
# remove the old hook in favor of the new above hook on same fileno
2102
1573
if command == 'funcall':
2103
1574
funcname = request[1]
2104
1575
args = request[2]
2105
1576
kwargs = request[3]
2107
parent_pipe.send(('data', getattr(client_object,
1578
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
2111
1580
if command == 'getattr':
2112
1581
attrname = request[1]
2113
1582
if callable(client_object.__getattribute__(attrname)):
2114
1583
parent_pipe.send(('function',))
2116
parent_pipe.send(('data', client_object
2117
.__getattribute__(attrname)))
1585
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
2119
1587
if command == 'setattr':
2120
1588
attrname = request[1]
2121
1589
value = request[2]
2122
1590
setattr(client_object, attrname, value)
2127
def rfc3339_duration_to_delta(duration):
2128
"""Parse an RFC 3339 "duration" and return a datetime.timedelta
2130
>>> rfc3339_duration_to_delta("P7D")
2131
datetime.timedelta(7)
2132
>>> rfc3339_duration_to_delta("PT60S")
2133
datetime.timedelta(0, 60)
2134
>>> rfc3339_duration_to_delta("PT60M")
2135
datetime.timedelta(0, 3600)
2136
>>> rfc3339_duration_to_delta("PT24H")
2137
datetime.timedelta(1)
2138
>>> rfc3339_duration_to_delta("P1W")
2139
datetime.timedelta(7)
2140
>>> rfc3339_duration_to_delta("PT5M30S")
2141
datetime.timedelta(0, 330)
2142
>>> rfc3339_duration_to_delta("P1DT3M20S")
2143
datetime.timedelta(1, 200)
2146
# Parsing an RFC 3339 duration with regular expressions is not
2147
# possible - there would have to be multiple places for the same
2148
# values, like seconds. The current code, while more esoteric, is
2149
# cleaner without depending on a parsing library. If Python had a
2150
# built-in library for parsing we would use it, but we'd like to
2151
# avoid excessive use of external libraries.
2153
# New type for defining tokens, syntax, and semantics all-in-one
2154
Token = collections.namedtuple("Token",
2155
("regexp", # To match token; if
2156
# "value" is not None,
2157
# must have a "group"
2159
"value", # datetime.timedelta or
2161
"followers")) # Tokens valid after
2163
# RFC 3339 "duration" tokens, syntax, and semantics; taken from
2164
# the "duration" ABNF definition in RFC 3339, Appendix A.
2165
token_end = Token(re.compile(r"$"), None, frozenset())
2166
token_second = Token(re.compile(r"(\d+)S"),
2167
datetime.timedelta(seconds=1),
2168
frozenset((token_end,)))
2169
token_minute = Token(re.compile(r"(\d+)M"),
2170
datetime.timedelta(minutes=1),
2171
frozenset((token_second, token_end)))
2172
token_hour = Token(re.compile(r"(\d+)H"),
2173
datetime.timedelta(hours=1),
2174
frozenset((token_minute, token_end)))
2175
token_time = Token(re.compile(r"T"),
2177
frozenset((token_hour, token_minute,
2179
token_day = Token(re.compile(r"(\d+)D"),
2180
datetime.timedelta(days=1),
2181
frozenset((token_time, token_end)))
2182
token_month = Token(re.compile(r"(\d+)M"),
2183
datetime.timedelta(weeks=4),
2184
frozenset((token_day, token_end)))
2185
token_year = Token(re.compile(r"(\d+)Y"),
2186
datetime.timedelta(weeks=52),
2187
frozenset((token_month, token_end)))
2188
token_week = Token(re.compile(r"(\d+)W"),
2189
datetime.timedelta(weeks=1),
2190
frozenset((token_end,)))
2191
token_duration = Token(re.compile(r"P"), None,
2192
frozenset((token_year, token_month,
2193
token_day, token_time,
2195
# Define starting values
2196
value = datetime.timedelta() # Value so far
2198
followers = frozenset(token_duration,) # Following valid tokens
2199
s = duration # String left to parse
2200
# Loop until end token is found
2201
while found_token is not token_end:
2202
# Search for any currently valid tokens
2203
for token in followers:
2204
match = token.regexp.match(s)
2205
if match is not None:
2207
if token.value is not None:
2208
# Value found, parse digits
2209
factor = int(match.group(1), 10)
2210
# Add to value so far
2211
value += factor * token.value
2212
# Strip token from string
2213
s = token.regexp.sub("", s, 1)
2216
# Set valid next tokens
2217
followers = found_token.followers
2220
# No currently valid tokens were found
2221
raise ValueError("Invalid RFC 3339 duration")
2226
1595
def string_to_delta(interval):
2227
1596
"""Parse a string and return a datetime.timedelta
2503
1856
.gnutls_global_set_log_function(debug_gnutls))
2505
1858
# Redirect stdin so all checkers get /dev/null
2506
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
1859
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2507
1860
os.dup2(null, sys.stdin.fileno())
1864
# No console logging
1865
logger.removeHandler(console)
2511
1867
# Need to fork before connecting to D-Bus
2513
1869
# Close all input and output, do double fork, etc.
2516
# multiprocessing will use threads, so before we use gobject we
2517
# need to inform gobject that threads will be used.
2518
gobject.threads_init()
2520
1872
global main_loop
2521
1873
# From the Avahi example code
2522
DBusGMainLoop(set_as_default=True)
1874
DBusGMainLoop(set_as_default=True )
2523
1875
main_loop = gobject.MainLoop()
2524
1876
bus = dbus.SystemBus()
2525
1877
# End of Avahi example code
2528
bus_name = dbus.service.BusName("se.recompile.Mandos",
1880
bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
2529
1881
bus, do_not_queue=True)
2530
old_bus_name = (dbus.service.BusName
2531
("se.bsnet.fukt.Mandos", bus,
2533
1882
except dbus.exceptions.NameExistsException as e:
2534
logger.error("Disabling D-Bus:", exc_info=e)
1883
logger.error(unicode(e) + ", disabling D-Bus")
2535
1884
use_dbus = False
2536
1885
server_settings["use_dbus"] = False
2537
1886
tcp_server.use_dbus = False
2538
1887
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2539
service = AvahiServiceToSyslog(name =
2540
server_settings["servicename"],
2541
servicetype = "_mandos._tcp",
2542
protocol = protocol, bus = bus)
1888
service = AvahiService(name = server_settings["servicename"],
1889
servicetype = "_mandos._tcp",
1890
protocol = protocol, bus = bus)
2543
1891
if server_settings["interface"]:
2544
1892
service.interface = (if_nametoindex
2545
1893
(str(server_settings["interface"])))
2550
1898
client_class = Client
2552
1900
client_class = functools.partial(ClientDBus, bus = bus)
2554
client_settings = Client.config_parser(client_config)
2555
old_client_settings = {}
2558
# This is used to redirect stdout and stderr for checker processes
2560
wnull = open(os.devnull, "w") # A writable /dev/null
2561
# Only used if server is running in foreground but not in debug
2563
if debug or not foreground:
2566
# Get client data and settings from last running state.
2567
if server_settings["restore"]:
2569
with open(stored_state_path, "rb") as stored_state:
2570
clients_data, old_client_settings = (pickle.load
2572
os.remove(stored_state_path)
2573
except IOError as e:
2574
if e.errno == errno.ENOENT:
2575
logger.warning("Could not load persistent state: {0}"
2576
.format(os.strerror(e.errno)))
2578
logger.critical("Could not load persistent state:",
2581
except EOFError as e:
2582
logger.warning("Could not load persistent state: "
2583
"EOFError:", exc_info=e)
2585
with PGPEngine() as pgp:
2586
for client_name, client in clients_data.iteritems():
2587
# Skip removed clients
2588
if client_name not in client_settings:
2591
# Decide which value to use after restoring saved state.
2592
# We have three different values: Old config file,
2593
# new config file, and saved state.
2594
# New config value takes precedence if it differs from old
2595
# config value, otherwise use saved state.
2596
for name, value in client_settings[client_name].items():
2598
# For each value in new config, check if it
2599
# differs from the old config value (Except for
2600
# the "secret" attribute)
2601
if (name != "secret" and
2602
value != old_client_settings[client_name]
2604
client[name] = value
2608
# Clients who has passed its expire date can still be
2609
# enabled if its last checker was successful. Clients
2610
# whose checker succeeded before we stored its state is
2611
# assumed to have successfully run all checkers during
2613
if client["enabled"]:
2614
if datetime.datetime.utcnow() >= client["expires"]:
2615
if not client["last_checked_ok"]:
2617
"disabling client {0} - Client never "
2618
"performed a successful checker"
2619
.format(client_name))
2620
client["enabled"] = False
2621
elif client["last_checker_status"] != 0:
2623
"disabling client {0} - Client "
2624
"last checker failed with error code {1}"
2625
.format(client_name,
2626
client["last_checker_status"]))
2627
client["enabled"] = False
2629
client["expires"] = (datetime.datetime
2631
+ client["timeout"])
2632
logger.debug("Last checker succeeded,"
2633
" keeping {0} enabled"
2634
.format(client_name))
1901
def client_config_items(config, section):
1902
special_settings = {
1903
"approved_by_default":
1904
lambda: config.getboolean(section,
1905
"approved_by_default"),
1907
for name, value in config.items(section):
2636
client["secret"] = (
2637
pgp.decrypt(client["encrypted_secret"],
2638
client_settings[client_name]
2641
# If decryption fails, we use secret from new settings
2642
logger.debug("Failed to decrypt {0} old secret"
2643
.format(client_name))
2644
client["secret"] = (
2645
client_settings[client_name]["secret"])
2647
# Add/remove clients based on new changes made to config
2648
for client_name in (set(old_client_settings)
2649
- set(client_settings)):
2650
del clients_data[client_name]
2651
for client_name in (set(client_settings)
2652
- set(old_client_settings)):
2653
clients_data[client_name] = client_settings[client_name]
2655
# Create all client objects
2656
for client_name, client in clients_data.iteritems():
2657
tcp_server.clients[client_name] = client_class(
2658
name = client_name, settings = client,
2659
server_settings = server_settings)
1909
yield (name, special_settings[name]())
1913
tcp_server.clients.update(set(
1914
client_class(name = section,
1915
config= dict(client_config_items(
1916
client_config, section)))
1917
for section in client_config.sections()))
2661
1918
if not tcp_server.clients:
2662
1919
logger.warning("No clients defined")
2665
if pidfile is not None:
2669
pidfile.write(str(pid) + "\n".encode("utf-8"))
2671
logger.error("Could not write to file %r with PID %d",
1925
pidfile.write(str(pid) + "\n".encode("utf-8"))
1928
logger.error("Could not write to file %r with PID %d",
1931
# "pidfile" was never created
2674
1933
del pidfilename
1935
signal.signal(signal.SIGINT, signal.SIG_IGN)
2676
1937
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2677
1938
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2680
@alternate_dbus_interfaces({"se.recompile.Mandos":
2681
"se.bsnet.fukt.Mandos"})
2682
class MandosDBusService(DBusObjectWithProperties):
1941
class MandosDBusService(dbus.service.Object):
2683
1942
"""A D-Bus proxy object"""
2684
1943
def __init__(self):
2685
1944
dbus.service.Object.__init__(self, bus, "/")
2686
_interface = "se.recompile.Mandos"
2688
@dbus_interface_annotations(_interface)
2690
return { "org.freedesktop.DBus.Property"
2691
".EmitsChangedSignal":
1945
_interface = "se.bsnet.fukt.Mandos"
2694
1947
@dbus.service.signal(_interface, signature="o")
2695
1948
def ClientAdded(self, objpath):
2744
1996
"Cleanup function; run on exit"
2745
1997
service.cleanup()
2747
multiprocessing.active_children()
2749
if not (tcp_server.clients or client_settings):
2752
# Store client before exiting. Secrets are encrypted with key
2753
# based on what config file has. If config file is
2754
# removed/edited, old secret will thus be unrecovable.
2756
with PGPEngine() as pgp:
2757
for client in tcp_server.clients.itervalues():
2758
key = client_settings[client.name]["secret"]
2759
client.encrypted_secret = pgp.encrypt(client.secret,
2763
# A list of attributes that can not be pickled
2765
exclude = set(("bus", "changedstate", "secret",
2766
"checker", "server_settings"))
2767
for name, typ in (inspect.getmembers
2768
(dbus.service.Object)):
2771
client_dict["encrypted_secret"] = (client
2773
for attr in client.client_structure:
2774
if attr not in exclude:
2775
client_dict[attr] = getattr(client, attr)
2777
clients[client.name] = client_dict
2778
del client_settings[client.name]["secret"]
2781
with (tempfile.NamedTemporaryFile
2782
(mode='wb', suffix=".pickle", prefix='clients-',
2783
dir=os.path.dirname(stored_state_path),
2784
delete=False)) as stored_state:
2785
pickle.dump((clients, client_settings), stored_state)
2786
tempname=stored_state.name
2787
os.rename(tempname, stored_state_path)
2788
except (IOError, OSError) as e:
2794
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2795
logger.warning("Could not save persistent state: {0}"
2796
.format(os.strerror(e.errno)))
2798
logger.warning("Could not save persistent state:",
2802
# Delete all clients, and settings from config
2803
1999
while tcp_server.clients:
2804
name, client = tcp_server.clients.popitem()
2000
client = tcp_server.clients.pop()
2806
2002
client.remove_from_connection()
2003
client.disable_hook = None
2807
2004
# Don't signal anything except ClientRemoved
2808
2005
client.disable(quiet=True)
2810
2007
# Emit D-Bus signal
2811
mandos_dbus_service.ClientRemoved(client
2008
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2814
client_settings.clear()
2816
2011
atexit.register(cleanup)
2818
for client in tcp_server.clients.itervalues():
2013
for client in tcp_server.clients:
2820
2015
# Emit D-Bus signal
2821
2016
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2822
# Need to initiate checking of clients
2824
client.init_checker()
2826
2019
tcp_server.enable()
2827
2020
tcp_server.server_activate()