110
110
except AttributeError:
111
111
shlex.quote = re.escape
113
# Add os.set_inheritable if it does not exist
116
except AttributeError:
117
def set_inheritable(fd, inheritable):
118
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
119
if inheritable and ((flags & fcntl.FD_CLOEXEC) != 0):
120
fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~fcntl.FD_CLOEXEC)
121
elif (not inheritable) and ((flags & fcntl.FD_CLOEXEC) == 0):
122
fcntl.fcntl(fd, fcntl.F_SETFL, flags | fcntl.FD_CLOEXEC)
123
os.set_inheritable = set_inheritable
126
113
# Show warnings by default
127
114
if not sys.warnoptions:
202
189
facility=logging.handlers.SysLogHandler.LOG_DAEMON,
203
190
address="/dev/log"))
204
191
syslogger.setFormatter(logging.Formatter
205
("Mandos [%(process)d]: %(levelname)s:"
207
log.addHandler(syslogger)
192
('Mandos [%(process)d]: %(levelname)s:'
194
logger.addHandler(syslogger)
210
197
console = logging.StreamHandler()
211
console.setFormatter(logging.Formatter("%(asctime)s %(name)s"
215
log.addHandler(console)
198
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
202
logger.addHandler(console)
203
logger.setLevel(level)
219
206
class PGPError(Exception):
237
224
except OSError as e:
238
225
if e.errno != errno.ENOENT:
240
self.gnupgargs = ["--batch",
241
"--homedir", self.tempdir,
227
self.gnupgargs = ['--batch',
228
'--homedir', self.tempdir,
244
231
# Only GPG version 1 has the --no-use-agent option.
245
232
if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"):
246
233
self.gnupgargs.append("--no-use-agent")
364
351
interface: integer; avahi.IF_UNSPEC or an interface index.
365
352
Used to optionally bind to the specified interface.
366
name: string; Example: "Mandos"
367
type: string; Example: "_mandos._tcp".
353
name: string; Example: 'Mandos'
354
type: string; Example: '_mandos._tcp'.
368
355
See <https://www.iana.org/assignments/service-names-port-numbers>
369
356
port: integer; what port to announce
370
357
TXT: list of strings; TXT record for the service
407
394
def rename(self, remove=True):
408
395
"""Derived from the Avahi example code"""
409
396
if self.rename_count >= self.max_renames:
410
log.critical("No suitable Zeroconf service name found"
411
" after %i retries, exiting.",
397
logger.critical("No suitable Zeroconf service name found"
398
" after %i retries, exiting.",
413
400
raise AvahiServiceError("Too many renames")
415
402
self.server.GetAlternativeServiceName(self.name))
416
403
self.rename_count += 1
417
log.info("Changing Zeroconf service name to %r ...",
404
logger.info("Changing Zeroconf service name to %r ...",
423
410
except dbus.exceptions.DBusException as error:
424
411
if (error.get_dbus_name()
425
412
== "org.freedesktop.Avahi.CollisionError"):
426
log.info("Local Zeroconf service name collision.")
413
logger.info("Local Zeroconf service name collision.")
427
414
return self.rename(remove=False)
429
log.critical("D-Bus Exception", exc_info=error)
416
logger.critical("D-Bus Exception", exc_info=error)
448
435
avahi.DBUS_INTERFACE_ENTRY_GROUP)
449
436
self.entry_group_state_changed_match = (
450
437
self.group.connect_to_signal(
451
"StateChanged", self.entry_group_state_changed))
452
log.debug("Adding Zeroconf service '%s' of type '%s' ...",
453
self.name, self.type)
438
'StateChanged', self.entry_group_state_changed))
439
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
440
self.name, self.type)
454
441
self.group.AddService(
464
451
def entry_group_state_changed(self, state, error):
465
452
"""Derived from the Avahi example code"""
466
log.debug("Avahi entry group state change: %i", state)
453
logger.debug("Avahi entry group state change: %i", state)
468
455
if state == avahi.ENTRY_GROUP_ESTABLISHED:
469
log.debug("Zeroconf service established.")
456
logger.debug("Zeroconf service established.")
470
457
elif state == avahi.ENTRY_GROUP_COLLISION:
471
log.info("Zeroconf service name collision.")
458
logger.info("Zeroconf service name collision.")
473
460
elif state == avahi.ENTRY_GROUP_FAILURE:
474
log.critical("Avahi: Error in group state changed %s",
461
logger.critical("Avahi: Error in group state changed %s",
476
463
raise AvahiGroupError("State changed: {!s}".format(error))
478
465
def cleanup(self):
489
476
def server_state_changed(self, state, error=None):
490
477
"""Derived from the Avahi example code"""
491
log.debug("Avahi server state change: %i", state)
478
logger.debug("Avahi server state change: %i", state)
493
480
avahi.SERVER_INVALID: "Zeroconf server invalid",
494
481
avahi.SERVER_REGISTERING: None,
508
495
except dbus.exceptions.DBusException as error:
509
496
if (error.get_dbus_name()
510
497
== "org.freedesktop.Avahi.CollisionError"):
511
log.info("Local Zeroconf service name collision.")
498
logger.info("Local Zeroconf service name"
512
500
return self.rename(remove=False)
514
log.critical("D-Bus Exception", exc_info=error)
502
logger.critical("D-Bus Exception", exc_info=error)
518
506
if error is None:
519
log.debug("Unknown state: %r", state)
507
logger.debug("Unknown state: %r", state)
521
log.debug("Unknown state: %r: %r", state, error)
509
logger.debug("Unknown state: %r: %r", state, error)
523
511
def activate(self):
524
512
"""Derived from the Avahi example code"""
698
686
def _retry_on_error(result, func, arguments,
699
687
_error_code=_error_code):
700
688
"""A function to retry on some errors, suitable
701
for the "errcheck" attribute on ctypes functions"""
689
for the 'errcheck' attribute on ctypes functions"""
702
690
while result < gnutls.E_SUCCESS:
703
691
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
704
692
return _error_code(result)
888
876
"""A representation of a client host served by this server.
891
approved: bool(); None if not yet approved/disapproved
879
approved: bool(); 'None' if not yet approved/disapproved
892
880
approval_delay: datetime.timedelta(); Time to wait for approval
893
881
approval_duration: datetime.timedelta(); Duration of one approval
894
882
checker: multiprocessing.Process(); a running checker process used
895
to see if the client lives. None if no process is
883
to see if the client lives. 'None' if no process is
897
885
checker_callback_tag: a GLib event source tag, or None
898
886
checker_command: string; External command which is run to check
974
962
# key_id() and fingerprint() functions
975
963
client["key_id"] = (section.get("key_id", "").upper()
976
964
.replace(" ", ""))
977
client["fingerprint"] = (section.get("fingerprint",
965
client["fingerprint"] = (section["fingerprint"].upper()
979
966
.replace(" ", ""))
980
if not (client["key_id"] or client["fingerprint"]):
981
log.error("Skipping client %s without key_id or"
982
" fingerprint", client_name)
983
del settings[client_name]
985
967
if "secret" in section:
986
968
client["secret"] = codecs.decode(section["secret"]
987
969
.encode("utf-8"),
1028
1010
self.last_enabled = None
1029
1011
self.expires = None
1031
log.debug("Creating client %r", self.name)
1032
log.debug(" Key ID: %s", self.key_id)
1033
log.debug(" Fingerprint: %s", self.fingerprint)
1013
logger.debug("Creating client %r", self.name)
1014
logger.debug(" Key ID: %s", self.key_id)
1015
logger.debug(" Fingerprint: %s", self.fingerprint)
1034
1016
self.created = settings.get("created",
1035
1017
datetime.datetime.utcnow())
1074
1057
if not getattr(self, "enabled", False):
1077
log.info("Disabling client %s", self.name)
1060
logger.info("Disabling client %s", self.name)
1078
1061
if getattr(self, "disable_initiator_tag", None) is not None:
1079
1062
GLib.source_remove(self.disable_initiator_tag)
1080
1063
self.disable_initiator_tag = None
1092
1075
def __del__(self):
1095
def init_checker(self, randomize_start=False):
1096
# Schedule a new checker to be started a randomly selected
1097
# time (a fraction of 'interval') from now. This spreads out
1098
# the startup of checkers over time when the server is
1078
def init_checker(self):
1079
# Schedule a new checker to be started an 'interval' from now,
1080
# and every interval from then on.
1100
1081
if self.checker_initiator_tag is not None:
1101
1082
GLib.source_remove(self.checker_initiator_tag)
1102
interval_milliseconds = int(self.interval.total_seconds()
1105
delay_milliseconds = random.randrange(
1106
interval_milliseconds + 1)
1108
delay_milliseconds = interval_milliseconds
1109
1083
self.checker_initiator_tag = GLib.timeout_add(
1110
delay_milliseconds, self.start_checker, randomize_start)
1111
delay = datetime.timedelta(0, 0, 0, delay_milliseconds)
1112
# A checker might take up to an 'interval' of time, so we can
1113
# expire at the soonest one interval after a checker was
1114
# started. Since the initial checker is delayed, the expire
1115
# time might have to be extended.
1116
now = datetime.datetime.utcnow()
1117
self.expires = now + delay + self.interval
1118
# Schedule a disable() at expire time
1084
random.randrange(int(self.interval.total_seconds() * 1000
1087
# Schedule a disable() when 'timeout' has passed
1119
1088
if self.disable_initiator_tag is not None:
1120
1089
GLib.source_remove(self.disable_initiator_tag)
1121
1090
self.disable_initiator_tag = GLib.timeout_add(
1122
int((self.expires - now).total_seconds() * 1000),
1091
int(self.timeout.total_seconds() * 1000), self.disable)
1092
# Also start a new checker *right now*.
1093
self.start_checker()
1125
1095
def checker_callback(self, source, condition, connection,
1137
1107
self.last_checker_status = returncode
1138
1108
self.last_checker_signal = None
1139
1109
if self.last_checker_status == 0:
1140
log.info("Checker for %(name)s succeeded", vars(self))
1110
logger.info("Checker for %(name)s succeeded",
1141
1112
self.checked_ok()
1143
log.info("Checker for %(name)s failed", vars(self))
1114
logger.info("Checker for %(name)s failed", vars(self))
1145
1116
self.last_checker_status = -1
1146
1117
self.last_checker_signal = -returncode
1147
log.warning("Checker for %(name)s crashed?", vars(self))
1118
logger.warning("Checker for %(name)s crashed?",
1150
1122
def checked_ok(self):
1197
1169
command = self.checker_command % escaped_attrs
1198
1170
except TypeError as error:
1199
log.error('Could not format string "%s"',
1200
self.checker_command, exc_info=error)
1171
logger.error('Could not format string "%s"',
1172
self.checker_command,
1201
1174
return True # Try again later
1202
1175
self.current_checker_command = command
1203
log.info("Starting checker %r for %s", command, self.name)
1176
logger.info("Starting checker %r for %s", command,
1204
1178
# We don't need to redirect stdout and stderr, since
1205
1179
# in normal mode, that is already done by daemon(),
1206
1180
# and in debug mode we don't want to. (Stdin is
1225
1199
GLib.IOChannel.unix_new(pipe[0].fileno()),
1226
1200
GLib.PRIORITY_DEFAULT, GLib.IO_IN,
1227
1201
self.checker_callback, pipe[0], command)
1228
if start_was_randomized:
1229
# We were started after a random delay; Schedule a new
1230
# checker to be started an 'interval' from now, and every
1231
# interval from then on.
1232
now = datetime.datetime.utcnow()
1233
self.checker_initiator_tag = GLib.timeout_add(
1234
int(self.interval.total_seconds() * 1000),
1236
self.expires = max(self.expires, now + self.interval)
1237
# Don't start a new checker again after same random delay
1239
1202
# Re-run this periodically if run by GLib.timeout_add
1375
1338
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1376
1339
out_signature="s",
1377
path_keyword="object_path",
1378
connection_keyword="connection")
1340
path_keyword='object_path',
1341
connection_keyword='connection')
1379
1342
def Introspect(self, object_path, connection):
1380
1343
"""Overloading of standard D-Bus method.
1535
1498
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1536
1499
out_signature="s",
1537
path_keyword="object_path",
1538
connection_keyword="connection")
1500
path_keyword='object_path',
1501
connection_keyword='connection')
1539
1502
def Introspect(self, object_path, connection):
1540
1503
"""Overloading of standard D-Bus method.
1637
1600
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
1638
1601
out_signature="s",
1639
path_keyword="object_path",
1640
connection_keyword="connection")
1602
path_keyword='object_path',
1603
connection_keyword='connection')
1641
1604
def Introspect(self, object_path, connection):
1642
1605
"""Overloading of standard D-Bus method.
2309
2272
class ProxyClient:
2310
2273
def __init__(self, child_pipe, key_id, fpr, address):
2311
2274
self._pipe = child_pipe
2312
self._pipe.send(("init", key_id, fpr, address))
2275
self._pipe.send(('init', key_id, fpr, address))
2313
2276
if not self._pipe.recv():
2314
2277
raise KeyError(key_id or fpr)
2316
2279
def __getattribute__(self, name):
2318
2281
return super(ProxyClient, self).__getattribute__(name)
2319
self._pipe.send(("getattr", name))
2282
self._pipe.send(('getattr', name))
2320
2283
data = self._pipe.recv()
2321
if data[0] == "data":
2284
if data[0] == 'data':
2323
if data[0] == "function":
2286
if data[0] == 'function':
2325
2288
def func(*args, **kwargs):
2326
self._pipe.send(("funcall", name, args, kwargs))
2289
self._pipe.send(('funcall', name, args, kwargs))
2327
2290
return self._pipe.recv()[1]
2331
2294
def __setattr__(self, name, value):
2333
2296
return super(ProxyClient, self).__setattr__(name, value)
2334
self._pipe.send(("setattr", name, value))
2297
self._pipe.send(('setattr', name, value))
2337
2300
class ClientHandler(socketserver.BaseRequestHandler, object):
2343
2306
def handle(self):
2344
2307
with contextlib.closing(self.server.child_pipe) as child_pipe:
2345
log.info("TCP connection from: %s",
2346
str(self.client_address))
2347
log.debug("Pipe FD: %d", self.server.child_pipe.fileno())
2308
logger.info("TCP connection from: %s",
2309
str(self.client_address))
2310
logger.debug("Pipe FD: %d",
2311
self.server.child_pipe.fileno())
2349
2313
session = gnutls.ClientSession(self.request)
2351
# priority = ":".join(("NONE", "+VERS-TLS1.1",
2315
# priority = ':'.join(("NONE", "+VERS-TLS1.1",
2352
2316
# "+AES-256-CBC", "+SHA1",
2353
2317
# "+COMP-NULL", "+CTYPE-OPENPGP",
2362
2326
# Start communication using the Mandos protocol
2363
2327
# Get protocol number
2364
2328
line = self.request.makefile().readline()
2365
log.debug("Protocol version: %r", line)
2329
logger.debug("Protocol version: %r", line)
2367
2331
if int(line.strip().split()[0]) > 1:
2368
2332
raise RuntimeError(line)
2369
2333
except (ValueError, IndexError, RuntimeError) as error:
2370
log.error("Unknown protocol version: %s", error)
2334
logger.error("Unknown protocol version: %s", error)
2373
2337
# Start GnuTLS connection
2375
2339
session.handshake()
2376
2340
except gnutls.Error as error:
2377
log.warning("Handshake failed: %s", error)
2341
logger.warning("Handshake failed: %s", error)
2378
2342
# Do not run session.bye() here: the session is not
2379
2343
# established. Just abandon the request.
2381
log.debug("Handshake succeeded")
2345
logger.debug("Handshake succeeded")
2383
2347
approval_required = False
2400
2364
fpr = self.fingerprint(
2401
2365
self.peer_certificate(session))
2402
2366
except (TypeError, gnutls.Error) as error:
2403
log.warning("Bad certificate: %s", error)
2367
logger.warning("Bad certificate: %s", error)
2405
log.debug("Fingerprint: %s", fpr)
2369
logger.debug("Fingerprint: %s", fpr)
2408
2372
client = ProxyClient(child_pipe, key_id, fpr,
2427
2392
# We are approved or approval is disabled
2429
2394
elif client.approved is None:
2430
log.info("Client %s needs approval",
2395
logger.info("Client %s needs approval",
2432
2397
if self.server.use_dbus:
2433
2398
# Emit D-Bus signal
2434
2399
client.NeedApproval(
2435
2400
client.approval_delay.total_seconds()
2436
2401
* 1000, client.approved_by_default)
2438
log.warning("Client %s was not approved",
2403
logger.warning("Client %s was not approved",
2440
2405
if self.server.use_dbus:
2441
2406
# Emit D-Bus signal
2442
2407
client.Rejected("Denied")
2466
2431
session.send(client.secret)
2467
2432
except gnutls.Error as error:
2468
log.warning("gnutls send failed", exc_info=error)
2433
logger.warning("gnutls send failed",
2471
log.info("Sending secret to %s", client.name)
2437
logger.info("Sending secret to %s", client.name)
2472
2438
# bump the timeout using extended_timeout
2473
2439
client.bump_timeout(client.extended_timeout)
2474
2440
if self.server.use_dbus:
2497
2464
valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
2498
2465
# If not a valid certificate type...
2499
2466
if cert_type not in valid_cert_types:
2500
log.info("Cert type %r not in %r", cert_type,
2467
logger.info("Cert type %r not in %r", cert_type,
2502
2469
# ...return invalid data
2504
2471
list_size = ctypes.c_uint(1)
2688
2655
(self.interface + "\0").encode("utf-8"))
2689
2656
except socket.error as error:
2690
2657
if error.errno == errno.EPERM:
2691
log.error("No permission to bind to interface %s",
2658
logger.error("No permission to bind to"
2659
" interface %s", self.interface)
2693
2660
elif error.errno == errno.ENOPROTOOPT:
2694
log.error("SO_BINDTODEVICE not available; cannot"
2695
" bind to interface %s", self.interface)
2661
logger.error("SO_BINDTODEVICE not available;"
2662
" cannot bind to interface %s",
2696
2664
elif error.errno == errno.ENODEV:
2697
log.error("Interface %s does not exist, cannot"
2698
" bind", self.interface)
2665
logger.error("Interface %s does not exist,"
2666
" cannot bind", self.interface)
2701
2669
# Only bind(2) the socket if we really need to.
2799
log.info("Client not found for key ID: %s, address:"
2800
" %s", key_id or fpr, address)
2767
logger.info("Client not found for key ID: %s, address"
2768
": %s", key_id or fpr, address)
2801
2769
if self.use_dbus:
2802
2770
# Emit D-Bus signal
2803
2771
mandos_dbus_service.ClientNotFound(key_id or fpr,
2816
2784
# remove the old hook in favor of the new above hook on
2819
if command == "funcall":
2787
if command == 'funcall':
2820
2788
funcname = request[1]
2821
2789
args = request[2]
2822
2790
kwargs = request[3]
2824
parent_pipe.send(("data", getattr(client_object,
2792
parent_pipe.send(('data', getattr(client_object,
2825
2793
funcname)(*args,
2828
if command == "getattr":
2796
if command == 'getattr':
2829
2797
attrname = request[1]
2830
2798
if isinstance(client_object.__getattribute__(attrname),
2831
2799
collections.abc.Callable):
2832
parent_pipe.send(("function", ))
2800
parent_pipe.send(('function', ))
2834
2802
parent_pipe.send((
2835
"data", client_object.__getattribute__(attrname)))
2803
'data', client_object.__getattribute__(attrname)))
2837
if command == "setattr":
2805
if command == 'setattr':
2838
2806
attrname = request[1]
2839
2807
value = request[2]
2840
2808
setattr(client_object, attrname, value)
2946
2914
def string_to_delta(interval):
2947
2915
"""Parse a string and return a datetime.timedelta
2949
>>> string_to_delta("7d") == datetime.timedelta(7)
2951
>>> string_to_delta("60s") == datetime.timedelta(0, 60)
2953
>>> string_to_delta("60m") == datetime.timedelta(0, 3600)
2955
>>> string_to_delta("24h") == datetime.timedelta(1)
2957
>>> string_to_delta("1w") == datetime.timedelta(7)
2959
>>> string_to_delta("5m 30s") == datetime.timedelta(0, 330)
2917
>>> string_to_delta('7d') == datetime.timedelta(7)
2919
>>> string_to_delta('60s') == datetime.timedelta(0, 60)
2921
>>> string_to_delta('60m') == datetime.timedelta(0, 3600)
2923
>>> string_to_delta('24h') == datetime.timedelta(1)
2925
>>> string_to_delta('1w') == datetime.timedelta(7)
2927
>>> string_to_delta('5m 30s') == datetime.timedelta(0, 330)
3109
3077
# Later, stdin will, and stdout and stderr might, be dup'ed
3110
3078
# over with an opened os.devnull. But we don't want this to
3111
3079
# happen with a supplied network socket.
3112
while 0 <= server_settings["socket"] <= 2:
3080
if 0 <= server_settings["socket"] <= 2:
3113
3081
server_settings["socket"] = os.dup(server_settings
3115
os.set_inheritable(server_settings["socket"], False)
3116
3083
del server_config
3118
3085
# Override the settings from the config file with command line
3199
3166
pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
3200
3167
except IOError as e:
3201
log.error("Could not open file %r", pidfilename,
3168
logger.error("Could not open file %r", pidfilename,
3204
3171
for name, group in (("_mandos", "_mandos"),
3205
3172
("mandos", "mandos"),
3219
log.debug("Did setuid/setgid to %s:%s", uid, gid)
3187
logger.debug("Did setuid/setgid to {}:{}".format(uid,
3220
3189
except OSError as error:
3221
log.warning("Failed to setuid/setgid to %s:%s: %s", uid, gid,
3222
os.strerror(error.errno))
3190
logger.warning("Failed to setuid/setgid to {}:{}: {}"
3191
.format(uid, gid, os.strerror(error.errno)))
3223
3192
if error.errno != errno.EPERM:
3360
3329
os.remove(stored_state_path)
3361
3330
except IOError as e:
3362
3331
if e.errno == errno.ENOENT:
3363
log.warning("Could not load persistent state:"
3364
" %s", os.strerror(e.errno))
3332
logger.warning("Could not load persistent state:"
3333
" {}".format(os.strerror(e.errno)))
3366
log.critical("Could not load persistent state:",
3335
logger.critical("Could not load persistent state:",
3369
3338
except EOFError as e:
3370
log.warning("Could not load persistent state: EOFError:",
3339
logger.warning("Could not load persistent state: "
3373
3343
with PGPEngine() as pgp:
3374
3344
for client_name, client in clients_data.items():
3401
3371
if client["enabled"]:
3402
3372
if datetime.datetime.utcnow() >= client["expires"]:
3403
3373
if not client["last_checked_ok"]:
3404
log.warning("disabling client %s - Client"
3405
" never performed a successful"
3406
" checker", client_name)
3375
"disabling client {} - Client never "
3376
"performed a successful checker".format(
3407
3378
client["enabled"] = False
3408
3379
elif client["last_checker_status"] != 0:
3409
log.warning("disabling client %s - Client"
3410
" last checker failed with error"
3411
" code %s", client_name,
3412
client["last_checker_status"])
3381
"disabling client {} - Client last"
3382
" checker failed with error code"
3385
client["last_checker_status"]))
3413
3386
client["enabled"] = False
3415
3388
client["expires"] = (
3416
3389
datetime.datetime.utcnow()
3417
3390
+ client["timeout"])
3418
log.debug("Last checker succeeded, keeping %s"
3419
" enabled", client_name)
3391
logger.debug("Last checker succeeded,"
3392
" keeping {} enabled".format(
3421
3395
client["secret"] = pgp.decrypt(
3422
3396
client["encrypted_secret"],
3423
3397
client_settings[client_name]["secret"])
3424
3398
except PGPError:
3425
3399
# If decryption fails, we use secret from new settings
3426
log.debug("Failed to decrypt %s old secret",
3400
logger.debug("Failed to decrypt {} old secret".format(
3428
3402
client["secret"] = (client_settings[client_name]
3626
3600
except NameError:
3628
3602
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
3629
log.warning("Could not save persistent state: %s",
3630
os.strerror(e.errno))
3603
logger.warning("Could not save persistent state: {}"
3604
.format(os.strerror(e.errno)))
3632
log.warning("Could not save persistent state:",
3606
logger.warning("Could not save persistent state:",
3636
3610
# Delete all clients, and settings from config
3663
3637
service.port = tcp_server.socket.getsockname()[1]
3665
log.info("Now listening on address %r, port %d, flowinfo %d,"
3666
" scope_id %d", *tcp_server.socket.getsockname())
3639
logger.info("Now listening on address %r, port %d,"
3640
" flowinfo %d, scope_id %d",
3641
*tcp_server.socket.getsockname())
3668
log.info("Now listening on address %r, port %d",
3669
*tcp_server.socket.getsockname())
3643
logger.info("Now listening on address %r, port %d",
3644
*tcp_server.socket.getsockname())
3671
3646
# service.interface = tcp_server.socket.getsockname()[3]
3687
3662
lambda *args, **kwargs: (tcp_server.handle_request
3688
3663
(*args[2:], **kwargs) or True))
3690
log.debug("Starting main loop")
3665
logger.debug("Starting main loop")
3691
3666
main_loop.run()
3692
3667
except AvahiError as error:
3693
log.critical("Avahi Error", exc_info=error)
3668
logger.critical("Avahi Error", exc_info=error)
3696
3671
except KeyboardInterrupt:
3698
3673
print("", file=sys.stderr)
3699
log.debug("Server received KeyboardInterrupt")
3700
log.debug("Server exiting")
3674
logger.debug("Server received KeyboardInterrupt")
3675
logger.debug("Server exiting")
3701
3676
# Must run before the D-Bus bus name gets deregistered
3705
def parse_test_args():
3706
# type: () -> argparse.Namespace
3680
def should_only_run_tests():
3707
3681
parser = argparse.ArgumentParser(add_help=False)
3708
parser.add_argument("--check", action="store_true")
3709
parser.add_argument("--prefix", )
3682
parser.add_argument("--check", action='store_true')
3710
3683
args, unknown_args = parser.parse_known_args()
3712
# Remove test options from sys.argv
3684
run_tests = args.check
3686
# Remove --check argument from sys.argv
3713
3687
sys.argv[1:] = unknown_args
3716
3690
# Add all tests from doctest strings
3717
3691
def load_tests(loader, tests, none):
3719
3693
tests.addTests(doctest.DocTestSuite())
3722
if __name__ == "__main__":
3723
options = parse_test_args()
3696
if __name__ == '__main__':
3726
extra_test_prefix = options.prefix
3727
if extra_test_prefix is not None:
3728
if not (unittest.main(argv=[""], exit=False)
3729
.result.wasSuccessful()):
3731
class ExtraTestLoader(unittest.TestLoader):
3732
testMethodPrefix = extra_test_prefix
3733
# Call using ./scriptname --test [--verbose]
3734
unittest.main(argv=[""], testLoader=ExtraTestLoader())
3736
unittest.main(argv=[""])
3698
if should_only_run_tests():
3699
# Call using ./mandos --check [--verbose]
3740
3704
logging.shutdown()
3744
# (lambda (&optional extra)
3745
# (if (not (funcall run-tests-in-test-buffer default-directory
3747
# (funcall show-test-buffer-in-test-window)
3748
# (funcall remove-test-window)
3749
# (if extra (message "Extra tests run successfully!"))))
3750
# run-tests-in-test-buffer:
3751
# (lambda (dir &optional extra)
3752
# (with-current-buffer (get-buffer-create "*Test*")
3753
# (setq buffer-read-only nil
3754
# default-directory dir)
3756
# (compilation-mode))
3757
# (let ((process-result
3758
# (let ((inhibit-read-only t))
3759
# (process-file-shell-command
3760
# (funcall get-command-line extra) nil "*Test*"))))
3761
# (and (numberp process-result)
3762
# (= process-result 0))))
3764
# (lambda (&optional extra)
3765
# (let ((quoted-script
3766
# (shell-quote-argument (funcall get-script-name))))
3768
# (concat "%s --check" (if extra " --prefix=atest" ""))
3772
# (if (fboundp 'file-local-name)
3773
# (file-local-name (buffer-file-name))
3774
# (or (file-remote-p (buffer-file-name) 'localname)
3775
# (buffer-file-name))))
3776
# remove-test-window:
3778
# (let ((test-window (get-buffer-window "*Test*")))
3779
# (if test-window (delete-window test-window))))
3780
# show-test-buffer-in-test-window:
3782
# (when (not (get-buffer-window-list "*Test*"))
3783
# (setq next-error-last-buffer (get-buffer "*Test*"))
3784
# (let* ((side (if (>= (window-width) 146) 'right 'bottom))
3785
# (display-buffer-overriding-action
3786
# `((display-buffer-in-side-window) (side . ,side)
3787
# (window-height . fit-window-to-buffer)
3788
# (window-width . fit-window-to-buffer))))
3789
# (display-buffer "*Test*"))))
3792
# (let* ((run-extra-tests (lambda () (interactive)
3793
# (funcall run-tests t)))
3794
# (inner-keymap `(keymap (116 . ,run-extra-tests))) ; t
3795
# (outer-keymap `(keymap (3 . ,inner-keymap)))) ; C-c
3796
# (setq minor-mode-overriding-map-alist
3797
# (cons `(run-tests . ,outer-keymap)
3798
# minor-mode-overriding-map-alist)))
3799
# (add-hook 'after-save-hook run-tests 90 t))