365
358
self.host = config.get("host", "")
366
359
self.created = datetime.datetime.utcnow()
368
361
self.last_approval_request = None
369
self.last_enabled = datetime.datetime.utcnow()
362
self.last_enabled = None
370
363
self.last_checked_ok = None
371
self.last_checker_status = 0
372
364
self.timeout = string_to_delta(config["timeout"])
373
365
self.extended_timeout = string_to_delta(config
374
366
["extended_timeout"])
375
367
self.interval = string_to_delta(config["interval"])
368
self.disable_hook = disable_hook
376
369
self.checker = None
377
370
self.checker_initiator_tag = None
378
371
self.disable_initiator_tag = None
379
self.expires = datetime.datetime.utcnow() + self.timeout
380
373
self.checker_callback_tag = None
381
374
self.checker_command = config["checker"]
382
375
self.current_checker_command = None
376
self.last_connect = None
383
377
self._approved = None
384
378
self.approved_by_default = config.get("approved_by_default",
412
397
# Already enabled
414
399
self.send_changedstate()
400
# Schedule a new checker to be started an 'interval' from now,
401
# and every interval from then on.
402
self.checker_initiator_tag = (gobject.timeout_add
403
(self.interval_milliseconds(),
405
# Schedule a disable() when 'timeout' has passed
415
406
self.expires = datetime.datetime.utcnow() + self.timeout
407
self.disable_initiator_tag = (gobject.timeout_add
408
(self.timeout_milliseconds(),
416
410
self.enabled = True
417
411
self.last_enabled = datetime.datetime.utcnow()
412
# Also start a new checker *right now*.
420
415
def disable(self, quiet=True):
421
416
"""Disable this client."""
433
428
gobject.source_remove(self.checker_initiator_tag)
434
429
self.checker_initiator_tag = None
435
430
self.stop_checker()
431
if self.disable_hook:
432
self.disable_hook(self)
436
433
self.enabled = False
437
434
# Do not run this again if called by a gobject.timeout_add
440
437
def __del__(self):
438
self.disable_hook = None
443
def init_checker(self):
444
# Schedule a new checker to be started an 'interval' from now,
445
# and every interval from then on.
446
self.checker_initiator_tag = (gobject.timeout_add
447
(self.interval_milliseconds(),
449
# Schedule a disable() when 'timeout' has passed
450
self.disable_initiator_tag = (gobject.timeout_add
451
(self.timeout_milliseconds(),
453
# Also start a new checker *right now*.
457
441
def checker_callback(self, pid, condition, command):
458
442
"""The checker has completed, so take appropriate actions."""
459
443
self.checker_callback_tag = None
460
444
self.checker = None
461
445
if os.WIFEXITED(condition):
462
self.last_checker_status = os.WEXITSTATUS(condition)
463
if self.last_checker_status == 0:
446
exitstatus = os.WEXITSTATUS(condition)
464
448
logger.info("Checker for %(name)s succeeded",
466
450
self.checked_ok()
481
464
if timeout is None:
482
465
timeout = self.timeout
483
466
self.last_checked_ok = datetime.datetime.utcnow()
484
if self.disable_initiator_tag is not None:
485
gobject.source_remove(self.disable_initiator_tag)
486
if getattr(self, "enabled", False):
487
self.disable_initiator_tag = (gobject.timeout_add
488
(_timedelta_to_milliseconds
489
(timeout), self.disable))
490
self.expires = datetime.datetime.utcnow() + timeout
467
gobject.source_remove(self.disable_initiator_tag)
468
self.disable_initiator_tag = (gobject.timeout_add
469
(_timedelta_to_milliseconds
470
(timeout), self.disable))
471
self.expires = datetime.datetime.utcnow() + timeout
492
473
def need_approval(self):
493
474
self.last_approval_request = datetime.datetime.utcnow()
586
567
self.checker = None
588
# Encrypts a client secret and stores it in a varible encrypted_secret
589
def encrypt_secret(self, key):
590
# Encryption-key need to be specific size, so we hash inputed key
591
hasheng = hashlib.sha256()
593
encryptionkey = hasheng.digest()
595
# Create validation hash so we know at decryption if it was sucessful
596
hasheng = hashlib.sha256()
597
hasheng.update(self.secret)
598
validationhash = hasheng.digest()
601
iv = os.urandom(Crypto.Cipher.AES.block_size)
602
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
603
Crypto.Cipher.AES.MODE_CFB, iv)
604
ciphertext = ciphereng.encrypt(validationhash+self.secret)
605
self.encrypted_secret = (ciphertext, iv)
607
# Decrypt a encrypted client secret
608
def decrypt_secret(self, key):
609
# Decryption-key need to be specific size, so we hash inputed key
610
hasheng = hashlib.sha256()
612
encryptionkey = hasheng.digest()
614
# Decrypt encrypted secret
615
ciphertext, iv = self.encrypted_secret
616
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
617
Crypto.Cipher.AES.MODE_CFB, iv)
618
plain = ciphereng.decrypt(ciphertext)
620
# Validate decrypted secret to know if it was succesful
621
hasheng = hashlib.sha256()
622
validationhash = plain[:hasheng.digest_size]
623
secret = plain[hasheng.digest_size:]
624
hasheng.update(secret)
626
# if validation fails, we use key as new secret. Otherwhise, we use
627
# the decrypted secret
628
if hasheng.digest() == validationhash:
632
del self.encrypted_secret
635
570
def dbus_service_property(dbus_interface, signature="v",
636
571
access="readwrite", byte_arrays=False):
2107
2037
client_class = functools.partial(ClientDBusTransitional,
2110
special_settings = {
2111
# Some settings need to be accessd by special methods;
2112
# booleans need .getboolean(), etc. Here is a list of them:
2113
"approved_by_default":
2115
client_config.getboolean(section, "approved_by_default"),
2117
# Construct a new dict of client settings of this form:
2118
# { client_name: {setting_name: value, ...}, ...}
2119
# with exceptions for any special settings as defined above
2120
client_settings = dict((clientname,
2122
(value if setting not in special_settings
2123
else special_settings[setting](clientname)))
2124
for setting, value in client_config.items(clientname)))
2125
for clientname in client_config.sections())
2127
old_client_settings = {}
2130
if server_settings["restore"]:
2132
with open(stored_state_path, "rb") as stored_state:
2133
clients_data, old_client_settings = pickle.load(stored_state)
2134
os.remove(stored_state_path)
2135
except IOError as e:
2136
logger.warning("Could not load persistant state: {0}".format(e))
2137
if e.errno != errno.ENOENT:
2140
for client in clients_data:
2141
client_name = client["name"]
2143
# Decide which value to use after restoring saved state.
2144
# We have three different values: Old config file,
2145
# new config file, and saved state.
2146
# New config value takes precedence if it differs from old
2147
# config value, otherwise use saved state.
2148
for name, value in client_settings[client_name].items():
2039
def client_config_items(config, section):
2040
special_settings = {
2041
"approved_by_default":
2042
lambda: config.getboolean(section,
2043
"approved_by_default"),
2045
for name, value in config.items(section):
2150
# For each value in new config, check if it differs
2151
# from the old config value (Except for the "secret"
2153
if name != "secret" and value != old_client_settings[client_name][name]:
2154
setattr(client, name, value)
2047
yield (name, special_settings[name]())
2155
2048
except KeyError:
2158
# Clients who has passed its expire date, can still be enabled if its
2159
# last checker was sucessful. Clients who checkers failed before we
2160
# stored it state is asumed to had failed checker during downtime.
2161
if client["enabled"] and client["last_checked_ok"]:
2162
if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2163
> client["interval"]):
2164
if client["last_checker_status"] != 0:
2165
client["enabled"] = False
2167
client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2169
client["changedstate"] = (multiprocessing_manager
2170
.Condition(multiprocessing_manager
2173
new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2174
tcp_server.clients[client_name] = new_client
2175
new_client.bus = bus
2176
for name, value in client.iteritems():
2177
setattr(new_client, name, value)
2178
client_object_name = unicode(client_name).translate(
2179
{ord("."): ord("_"),
2180
ord("-"): ord("_")})
2181
new_client.dbus_object_path = (dbus.ObjectPath
2182
("/clients/" + client_object_name))
2183
DBusObjectWithProperties.__init__(new_client,
2185
new_client.dbus_object_path)
2187
tcp_server.clients[client_name] = Client.__new__(Client)
2188
for name, value in client.iteritems():
2189
setattr(tcp_server.clients[client_name], name, value)
2191
tcp_server.clients[client_name].decrypt_secret(
2192
client_settings[client_name]["secret"])
2194
# Create/remove clients based on new changes made to config
2195
for clientname in set(old_client_settings) - set(client_settings):
2196
del tcp_server.clients[clientname]
2197
for clientname in set(client_settings) - set(old_client_settings):
2198
tcp_server.clients[clientname] = (client_class(name = clientname,
2051
tcp_server.clients.update(set(
2052
client_class(name = section,
2053
config= dict(client_config_items(
2054
client_config, section)))
2055
for section in client_config.sections()))
2204
2056
if not tcp_server.clients:
2205
2057
logger.warning("No clients defined")
2259
2110
return dbus.Dictionary(
2260
2111
((c.dbus_object_path, c.GetAll(""))
2261
for c in tcp_server.clients.itervalues()),
2112
for c in tcp_server.clients),
2262
2113
signature="oa{sv}")
2264
2115
@dbus.service.method(_interface, in_signature="o")
2265
2116
def RemoveClient(self, object_path):
2267
for c in tcp_server.clients.itervalues():
2118
for c in tcp_server.clients:
2268
2119
if c.dbus_object_path == object_path:
2269
del tcp_server.clients[c.name]
2120
tcp_server.clients.remove(c)
2270
2121
c.remove_from_connection()
2271
2122
# Don't signal anything except ClientRemoved
2272
2123
c.disable(quiet=True)
2286
2137
service.cleanup()
2288
2139
multiprocessing.active_children()
2289
if not (tcp_server.clients or client_settings):
2292
# Store client before exiting. Secrets are encrypted with key based
2293
# on what config file has. If config file is removed/edited, old
2294
# secret will thus be unrecovable.
2296
for client in tcp_server.clients.itervalues():
2297
client.encrypt_secret(client_settings[client.name]["secret"])
2301
# A list of attributes that will not be stored when shuting down.
2302
exclude = set(("bus", "changedstate", "secret"))
2303
for name, typ in inspect.getmembers(dbus.service.Object):
2306
client_dict["encrypted_secret"] = client.encrypted_secret
2307
for attr in client.client_structure:
2308
if attr not in exclude:
2309
client_dict[attr] = getattr(client, attr)
2311
clients.append(client_dict)
2312
del client_settings[client.name]["secret"]
2315
with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
2316
pickle.dump((clients, client_settings), stored_state)
2317
except IOError as e:
2318
logger.warning("Could not save persistant state: {0}".format(e))
2319
if e.errno != errno.ENOENT:
2322
# Delete all clients, and settings from config
2323
2140
while tcp_server.clients:
2324
name, client = tcp_server.clients.popitem()
2141
client = tcp_server.clients.pop()
2326
2143
client.remove_from_connection()
2144
client.disable_hook = None
2327
2145
# Don't signal anything except ClientRemoved
2328
2146
client.disable(quiet=True)
2330
2148
# Emit D-Bus signal
2331
2149
mandos_dbus_service.ClientRemoved(client
2334
client_settings.clear()
2336
2153
atexit.register(cleanup)
2338
for client in tcp_server.clients.itervalues():
2155
for client in tcp_server.clients:
2340
2157
# Emit D-Bus signal
2341
2158
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2342
# Need to initiate checking of clients
2344
client.init_checker()
2347
2161
tcp_server.enable()
2348
2162
tcp_server.server_activate()