89
stored_state_path = "/var/lib/mandos/clients.pickle"
91
logger = logging.getLogger()
88
#logger = logging.getLogger('mandos')
89
logger = logging.Logger('mandos')
92
90
syslogger = (logging.handlers.SysLogHandler
93
91
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
94
92
address = str("/dev/log")))
97
if_nametoindex = (ctypes.cdll.LoadLibrary
98
(ctypes.util.find_library("c"))
100
except (OSError, AttributeError):
101
def if_nametoindex(interface):
102
"Get an interface index the hard way, i.e. using fcntl()"
103
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
104
with contextlib.closing(socket.socket()) as s:
105
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
106
struct.pack(str("16s16x"),
108
interface_index = struct.unpack(str("I"),
110
return interface_index
113
def initlogger(level=logging.WARNING):
114
"""init logger and add loglevel"""
116
syslogger.setFormatter(logging.Formatter
117
('Mandos [%(process)d]: %(levelname)s:'
119
logger.addHandler(syslogger)
121
console = logging.StreamHandler()
122
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
126
logger.addHandler(console)
127
logger.setLevel(level)
93
syslogger.setFormatter(logging.Formatter
94
('Mandos [%(process)d]: %(levelname)s:'
96
logger.addHandler(syslogger)
98
console = logging.StreamHandler()
99
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
102
logger.addHandler(console)
130
104
class AvahiError(Exception):
131
105
def __init__(self, value, *args, **kwargs):
393
358
self.host = config.get("host", "")
394
359
self.created = datetime.datetime.utcnow()
396
361
self.last_approval_request = None
397
self.last_enabled = datetime.datetime.utcnow()
362
self.last_enabled = None
398
363
self.last_checked_ok = None
399
self.last_checker_status = None
400
364
self.timeout = string_to_delta(config["timeout"])
401
365
self.extended_timeout = string_to_delta(config
402
366
["extended_timeout"])
403
367
self.interval = string_to_delta(config["interval"])
368
self.disable_hook = disable_hook
404
369
self.checker = None
405
370
self.checker_initiator_tag = None
406
371
self.disable_initiator_tag = None
407
self.expires = datetime.datetime.utcnow() + self.timeout
408
373
self.checker_callback_tag = None
409
374
self.checker_command = config["checker"]
410
375
self.current_checker_command = None
376
self.last_connect = None
411
377
self._approved = None
412
378
self.approved_by_default = config.get("approved_by_default",
419
385
self.changedstate = (multiprocessing_manager
420
386
.Condition(multiprocessing_manager
422
self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
423
self.client_structure.append("client_structure")
426
for name, t in inspect.getmembers(type(self),
427
lambda obj: isinstance(obj, property)):
428
if not name.startswith("_"):
429
self.client_structure.append(name)
431
# Send notice to process children that client state has changed
432
389
def send_changedstate(self):
433
with self.changedstate:
434
self.changedstate.notify_all()
390
self.changedstate.acquire()
391
self.changedstate.notify_all()
392
self.changedstate.release()
436
394
def enable(self):
437
395
"""Start this client's checker and timeout hooks"""
460
428
gobject.source_remove(self.checker_initiator_tag)
461
429
self.checker_initiator_tag = None
462
430
self.stop_checker()
431
if self.disable_hook:
432
self.disable_hook(self)
463
433
self.enabled = False
464
434
# Do not run this again if called by a gobject.timeout_add
467
437
def __del__(self):
438
self.disable_hook = None
470
def init_checker(self):
471
# Schedule a new checker to be started an 'interval' from now,
472
# and every interval from then on.
473
self.checker_initiator_tag = (gobject.timeout_add
474
(self.interval_milliseconds(),
476
# Schedule a disable() when 'timeout' has passed
477
self.disable_initiator_tag = (gobject.timeout_add
478
(self.timeout_milliseconds(),
480
# Also start a new checker *right now*.
484
441
def checker_callback(self, pid, condition, command):
485
442
"""The checker has completed, so take appropriate actions."""
486
443
self.checker_callback_tag = None
487
444
self.checker = None
488
445
if os.WIFEXITED(condition):
489
self.last_checker_status = os.WEXITSTATUS(condition)
490
if self.last_checker_status == 0:
446
exitstatus = os.WEXITSTATUS(condition)
491
448
logger.info("Checker for %(name)s succeeded",
493
450
self.checked_ok()
613
569
self.checker = None
615
# Encrypts a client secret and stores it in a varible encrypted_secret
616
def encrypt_secret(self, key):
617
# Encryption-key need to be of a specific size, so we hash inputed key
618
hasheng = hashlib.sha256()
620
encryptionkey = hasheng.digest()
622
# Create validation hash so we know at decryption if it was sucessful
623
hasheng = hashlib.sha256()
624
hasheng.update(self.secret)
625
validationhash = hasheng.digest()
628
iv = os.urandom(Crypto.Cipher.AES.block_size)
629
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
630
Crypto.Cipher.AES.MODE_CFB, iv)
631
ciphertext = ciphereng.encrypt(validationhash+self.secret)
632
self.encrypted_secret = (ciphertext, iv)
634
# Decrypt a encrypted client secret
635
def decrypt_secret(self, key):
636
# Decryption-key need to be of a specific size, so we hash inputed key
637
hasheng = hashlib.sha256()
639
encryptionkey = hasheng.digest()
641
# Decrypt encrypted secret
642
ciphertext, iv = self.encrypted_secret
643
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
644
Crypto.Cipher.AES.MODE_CFB, iv)
645
plain = ciphereng.decrypt(ciphertext)
647
# Validate decrypted secret to know if it was succesful
648
hasheng = hashlib.sha256()
649
validationhash = plain[:hasheng.digest_size]
650
secret = plain[hasheng.digest_size:]
651
hasheng.update(secret)
653
# if validation fails, we use key as new secret. Otherwhise, we use
654
# the decrypted secret
655
if hasheng.digest() == validationhash:
659
del self.encrypted_secret
662
572
def dbus_service_property(dbus_interface, signature="v",
663
573
access="readwrite", byte_arrays=False):
1869
1767
return timevalue
1770
def if_nametoindex(interface):
1771
"""Call the C function if_nametoindex(), or equivalent
1773
Note: This function cannot accept a unicode string."""
1774
global if_nametoindex
1776
if_nametoindex = (ctypes.cdll.LoadLibrary
1777
(ctypes.util.find_library("c"))
1779
except (OSError, AttributeError):
1780
logger.warning("Doing if_nametoindex the hard way")
1781
def if_nametoindex(interface):
1782
"Get an interface index the hard way, i.e. using fcntl()"
1783
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1784
with contextlib.closing(socket.socket()) as s:
1785
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1786
struct.pack(str("16s16x"),
1788
interface_index = struct.unpack(str("I"),
1790
return interface_index
1791
return if_nametoindex(interface)
1872
1794
def daemon(nochdir = False, noclose = False):
1873
1795
"""See daemon(3). Standard BSD Unix function.
2107
2024
server_settings["use_dbus"] = False
2108
2025
tcp_server.use_dbus = False
2109
2026
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2110
service = AvahiServiceToSyslog(name =
2111
server_settings["servicename"],
2112
servicetype = "_mandos._tcp",
2113
protocol = protocol, bus = bus)
2027
service = AvahiService(name = server_settings["servicename"],
2028
servicetype = "_mandos._tcp",
2029
protocol = protocol, bus = bus)
2114
2030
if server_settings["interface"]:
2115
2031
service.interface = (if_nametoindex
2116
2032
(str(server_settings["interface"])))
2123
2039
client_class = functools.partial(ClientDBusTransitional,
2126
special_settings = {
2127
# Some settings need to be accessd by special methods;
2128
# booleans need .getboolean(), etc. Here is a list of them:
2129
"approved_by_default":
2131
client_config.getboolean(section, "approved_by_default"),
2133
# Construct a new dict of client settings of this form:
2134
# { client_name: {setting_name: value, ...}, ...}
2135
# with exceptions for any special settings as defined above
2136
client_settings = dict((clientname,
2138
(value if setting not in special_settings
2139
else special_settings[setting](clientname)))
2140
for setting, value in client_config.items(clientname)))
2141
for clientname in client_config.sections())
2143
old_client_settings = {}
2146
# Get client data and settings from last running state.
2147
if server_settings["restore"]:
2149
with open(stored_state_path, "rb") as stored_state:
2150
clients_data, old_client_settings = pickle.load(stored_state)
2151
os.remove(stored_state_path)
2152
except IOError as e:
2153
logger.warning("Could not load persistant state: {0}".format(e))
2154
if e.errno != errno.ENOENT:
2157
for client in clients_data:
2158
client_name = client["name"]
2160
# Decide which value to use after restoring saved state.
2161
# We have three different values: Old config file,
2162
# new config file, and saved state.
2163
# New config value takes precedence if it differs from old
2164
# config value, otherwise use saved state.
2165
for name, value in client_settings[client_name].items():
2041
def client_config_items(config, section):
2042
special_settings = {
2043
"approved_by_default":
2044
lambda: config.getboolean(section,
2045
"approved_by_default"),
2047
for name, value in config.items(section):
2167
# For each value in new config, check if it differs
2168
# from the old config value (Except for the "secret"
2170
if name != "secret" and value != old_client_settings[client_name][name]:
2171
setattr(client, name, value)
2049
yield (name, special_settings[name]())
2172
2050
except KeyError:
2175
# Clients who has passed its expire date, can still be enabled if its
2176
# last checker was sucessful. Clients who checkers failed before we
2177
# stored it state is asumed to had failed checker during downtime.
2178
if client["enabled"] and client["last_checked_ok"]:
2179
if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2180
> client["interval"]):
2181
if client["last_checker_status"] != 0:
2182
client["enabled"] = False
2184
client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2186
client["changedstate"] = (multiprocessing_manager
2187
.Condition(multiprocessing_manager
2190
new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2191
tcp_server.clients[client_name] = new_client
2192
new_client.bus = bus
2193
for name, value in client.iteritems():
2194
setattr(new_client, name, value)
2195
client_object_name = unicode(client_name).translate(
2196
{ord("."): ord("_"),
2197
ord("-"): ord("_")})
2198
new_client.dbus_object_path = (dbus.ObjectPath
2199
("/clients/" + client_object_name))
2200
DBusObjectWithProperties.__init__(new_client,
2202
new_client.dbus_object_path)
2204
tcp_server.clients[client_name] = Client.__new__(Client)
2205
for name, value in client.iteritems():
2206
setattr(tcp_server.clients[client_name], name, value)
2208
tcp_server.clients[client_name].decrypt_secret(
2209
client_settings[client_name]["secret"])
2211
# Create/remove clients based on new changes made to config
2212
for clientname in set(old_client_settings) - set(client_settings):
2213
del tcp_server.clients[clientname]
2214
for clientname in set(client_settings) - set(old_client_settings):
2215
tcp_server.clients[clientname] = (client_class(name = clientname,
2053
tcp_server.clients.update(set(
2054
client_class(name = section,
2055
config= dict(client_config_items(
2056
client_config, section)))
2057
for section in client_config.sections()))
2221
2058
if not tcp_server.clients:
2222
2059
logger.warning("No clients defined")
2303
2139
service.cleanup()
2305
2141
multiprocessing.active_children()
2306
if not (tcp_server.clients or client_settings):
2309
# Store client before exiting. Secrets are encrypted with key based
2310
# on what config file has. If config file is removed/edited, old
2311
# secret will thus be unrecovable.
2313
for client in tcp_server.clients.itervalues():
2314
client.encrypt_secret(client_settings[client.name]["secret"])
2318
# A list of attributes that will not be stored when shuting down.
2319
exclude = set(("bus", "changedstate", "secret"))
2320
for name, typ in inspect.getmembers(dbus.service.Object):
2323
client_dict["encrypted_secret"] = client.encrypted_secret
2324
for attr in client.client_structure:
2325
if attr not in exclude:
2326
client_dict[attr] = getattr(client, attr)
2328
clients.append(client_dict)
2329
del client_settings[client.name]["secret"]
2332
with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
2333
pickle.dump((clients, client_settings), stored_state)
2334
except IOError as e:
2335
logger.warning("Could not save persistant state: {0}".format(e))
2336
if e.errno != errno.ENOENT:
2339
# Delete all clients, and settings from config
2340
2142
while tcp_server.clients:
2341
name, client = tcp_server.clients.popitem()
2143
client = tcp_server.clients.pop()
2343
2145
client.remove_from_connection()
2146
client.disable_hook = None
2344
2147
# Don't signal anything except ClientRemoved
2345
2148
client.disable(quiet=True)