34
34
from __future__ import (division, absolute_import, print_function,
37
from future_builtins import *
38
from future_builtins import *
40
43
import SocketServer as socketserver
78
81
import dbus.service
82
from gi.repository import GObject as gobject
82
from gi.repository import GLib
84
83
from dbus.mainloop.glib import DBusGMainLoop
87
86
import xml.dom.minidom
89
# Try to find the value of SO_BINDTODEVICE:
91
# This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
92
# newer, and it is also the most natural place for it:
91
93
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
92
94
except AttributeError:
96
# This is where SO_BINDTODEVICE was up to and including Python
94
98
from IN import SO_BINDTODEVICE
95
99
except ImportError:
96
SO_BINDTODEVICE = None
100
# In Python 2.7 it seems to have been removed entirely.
101
# Try running the C preprocessor:
103
cc = subprocess.Popen(["cc", "--language=c", "-E",
105
stdin=subprocess.PIPE,
106
stdout=subprocess.PIPE)
107
stdout = cc.communicate(
108
"#include <sys/socket.h>\nSO_BINDTODEVICE\n")[0]
109
SO_BINDTODEVICE = int(stdout.splitlines()[-1])
110
except (OSError, ValueError, IndexError):
112
SO_BINDTODEVICE = None
98
114
if sys.version_info.major == 2:
102
118
stored_state_file = "clients.pickle"
104
120
logger = logging.getLogger()
119
135
return interface_index
138
def copy_function(func):
139
"""Make a copy of a function"""
140
if sys.version_info.major == 2:
141
return types.FunctionType(func.func_code,
147
return types.FunctionType(func.__code__,
122
154
def initlogger(debug, level=logging.WARNING):
123
155
"""init logger and add loglevel"""
156
188
output = subprocess.check_output(["gpgconf"])
157
189
for line in output.splitlines():
158
name, text, path = line.split(":")
190
name, text, path = line.split(b":")
159
191
if name == "gpg":
165
197
self.gnupgargs = ['--batch',
166
198
'--homedir', self.tempdir,
201
# Only GPG version 1 has the --no-use-agent option.
202
if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
203
self.gnupgargs.append("--no-use-agent")
171
205
def __enter__(self):
238
272
raise PGPError(err)
239
273
return decrypted_plaintext
275
# Pretend that we have an Avahi module
277
"""This isn't so much a class as it is a module-like namespace.
278
It is instantiated once, and simulates having an Avahi module."""
279
IF_UNSPEC = -1 # avahi-common/address.h
280
PROTO_UNSPEC = -1 # avahi-common/address.h
281
PROTO_INET = 0 # avahi-common/address.h
282
PROTO_INET6 = 1 # avahi-common/address.h
283
DBUS_NAME = "org.freedesktop.Avahi"
284
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
285
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
286
DBUS_PATH_SERVER = "/"
287
def string_array_to_txt_array(self, t):
288
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
289
for s in t), signature="ay")
290
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
291
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
292
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
293
SERVER_INVALID = 0 # avahi-common/defs.h
294
SERVER_REGISTERING = 1 # avahi-common/defs.h
295
SERVER_RUNNING = 2 # avahi-common/defs.h
296
SERVER_COLLISION = 3 # avahi-common/defs.h
297
SERVER_FAILURE = 4 # avahi-common/defs.h
242
300
class AvahiError(Exception):
243
301
def __init__(self, value, *args, **kwargs):
487
545
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
488
546
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
489
547
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
490
credentials_type_t = ctypes.c_int #
548
credentials_type_t = ctypes.c_int
491
549
transport_ptr_t = ctypes.c_void_p
492
550
close_request_t = ctypes.c_int
715
773
checker: subprocess.Popen(); a running checker process used
716
774
to see if the client lives.
717
775
'None' if no process is running.
718
checker_callback_tag: a gobject event source tag, or None
776
checker_callback_tag: a GLib event source tag, or None
719
777
checker_command: string; External command which is run to check
720
778
if client lives. %() expansions are done at
721
779
runtime with vars(self) as dict, so that for
722
780
instance %(name)s can be used in the command.
723
checker_initiator_tag: a gobject event source tag, or None
781
checker_initiator_tag: a GLib event source tag, or None
724
782
created: datetime.datetime(); (UTC) object creation
725
783
client_structure: Object describing what attributes a client has
726
784
and is used for storing the client at exit
727
785
current_checker_command: string; current running checker_command
728
disable_initiator_tag: a gobject event source tag, or None
786
disable_initiator_tag: a GLib event source tag, or None
730
788
fingerprint: string (40 or 32 hexadecimal digits); used to
731
789
uniquely identify the client
794
852
client["fingerprint"] = (section["fingerprint"].upper()
795
853
.replace(" ", ""))
796
854
if "secret" in section:
797
client["secret"] = section["secret"].decode("base64")
855
client["secret"] = codecs.decode(section["secret"]
798
858
elif "secfile" in section:
799
859
with open(os.path.expanduser(os.path.expandvars
800
860
(section["secfile"])),
853
913
self.changedstate = multiprocessing_manager.Condition(
854
914
multiprocessing_manager.Lock())
855
915
self.client_structure = [attr
856
for attr in self.__dict__.iterkeys()
916
for attr in self.__dict__.keys()
857
917
if not attr.startswith("_")]
858
918
self.client_structure.append("client_structure")
886
946
logger.info("Disabling client %s", self.name)
887
947
if getattr(self, "disable_initiator_tag", None) is not None:
888
gobject.source_remove(self.disable_initiator_tag)
948
GLib.source_remove(self.disable_initiator_tag)
889
949
self.disable_initiator_tag = None
890
950
self.expires = None
891
951
if getattr(self, "checker_initiator_tag", None) is not None:
892
gobject.source_remove(self.checker_initiator_tag)
952
GLib.source_remove(self.checker_initiator_tag)
893
953
self.checker_initiator_tag = None
894
954
self.stop_checker()
895
955
self.enabled = False
897
957
self.send_changedstate()
898
# Do not run this again if called by a gobject.timeout_add
958
# Do not run this again if called by a GLib.timeout_add
901
961
def __del__(self):
905
965
# Schedule a new checker to be started an 'interval' from now,
906
966
# and every interval from then on.
907
967
if self.checker_initiator_tag is not None:
908
gobject.source_remove(self.checker_initiator_tag)
909
self.checker_initiator_tag = gobject.timeout_add(
968
GLib.source_remove(self.checker_initiator_tag)
969
self.checker_initiator_tag = GLib.timeout_add(
910
970
int(self.interval.total_seconds() * 1000),
911
971
self.start_checker)
912
972
# Schedule a disable() when 'timeout' has passed
913
973
if self.disable_initiator_tag is not None:
914
gobject.source_remove(self.disable_initiator_tag)
915
self.disable_initiator_tag = gobject.timeout_add(
974
GLib.source_remove(self.disable_initiator_tag)
975
self.disable_initiator_tag = GLib.timeout_add(
916
976
int(self.timeout.total_seconds() * 1000), self.disable)
917
977
# Also start a new checker *right now*.
918
978
self.start_checker()
954
1014
if timeout is None:
955
1015
timeout = self.timeout
956
1016
if self.disable_initiator_tag is not None:
957
gobject.source_remove(self.disable_initiator_tag)
1017
GLib.source_remove(self.disable_initiator_tag)
958
1018
self.disable_initiator_tag = None
959
1019
if getattr(self, "enabled", False):
960
self.disable_initiator_tag = gobject.timeout_add(
1020
self.disable_initiator_tag = GLib.timeout_add(
961
1021
int(timeout.total_seconds() * 1000), self.disable)
962
1022
self.expires = datetime.datetime.utcnow() + timeout
1018
1078
args = (pipe[1], subprocess.call, command),
1019
1079
kwargs = popen_args)
1020
1080
self.checker.start()
1021
self.checker_callback_tag = gobject.io_add_watch(
1022
pipe[0].fileno(), gobject.IO_IN,
1081
self.checker_callback_tag = GLib.io_add_watch(
1082
pipe[0].fileno(), GLib.IO_IN,
1023
1083
self.checker_callback, pipe[0], command)
1024
# Re-run this periodically if run by gobject.timeout_add
1084
# Re-run this periodically if run by GLib.timeout_add
1027
1087
def stop_checker(self):
1028
1088
"""Force the checker process, if any, to stop."""
1029
1089
if self.checker_callback_tag:
1030
gobject.source_remove(self.checker_callback_tag)
1090
GLib.source_remove(self.checker_callback_tag)
1031
1091
self.checker_callback_tag = None
1032
1092
if getattr(self, "checker", None) is None:
1507
1567
interface_names.add(alt_interface)
1508
1568
# Is this a D-Bus signal?
1509
1569
if getattr(attribute, "_dbus_is_signal", False):
1570
# Extract the original non-method undecorated
1571
# function by black magic
1510
1572
if sys.version_info.major == 2:
1511
# Extract the original non-method undecorated
1512
# function by black magic
1513
1573
nonmethod_func = (dict(
1514
1574
zip(attribute.func_code.co_freevars,
1515
1575
attribute.__closure__))
1516
1576
["func"].cell_contents)
1518
nonmethod_func = attribute
1578
nonmethod_func = (dict(
1579
zip(attribute.__code__.co_freevars,
1580
attribute.__closure__))
1581
["func"].cell_contents)
1519
1582
# Create a new, but exactly alike, function
1520
1583
# object, and decorate it to be a new D-Bus signal
1521
1584
# with the alternate D-Bus interface name
1522
if sys.version_info.major == 2:
1523
new_function = types.FunctionType(
1524
nonmethod_func.func_code,
1525
nonmethod_func.func_globals,
1526
nonmethod_func.func_name,
1527
nonmethod_func.func_defaults,
1528
nonmethod_func.func_closure)
1530
new_function = types.FunctionType(
1531
nonmethod_func.__code__,
1532
nonmethod_func.__globals__,
1533
nonmethod_func.__name__,
1534
nonmethod_func.__defaults__,
1535
nonmethod_func.__closure__)
1585
new_function = copy_function(nonmethod_func)
1536
1586
new_function = (dbus.service.signal(
1538
1588
attribute._dbus_signature)(new_function))
1578
1628
attribute._dbus_in_signature,
1579
1629
attribute._dbus_out_signature)
1580
(types.FunctionType(attribute.func_code,
1581
attribute.func_globals,
1582
attribute.func_name,
1583
attribute.func_defaults,
1584
attribute.func_closure)))
1630
(copy_function(attribute)))
1585
1631
# Copy annotations, if any
1587
1633
attr[attrname]._dbus_annotations = dict(
1599
1645
attribute._dbus_access,
1600
1646
attribute._dbus_get_args_options
1601
1647
["byte_arrays"])
1602
(types.FunctionType(
1603
attribute.func_code,
1604
attribute.func_globals,
1605
attribute.func_name,
1606
attribute.func_defaults,
1607
attribute.func_closure)))
1648
(copy_function(attribute)))
1608
1649
# Copy annotations, if any
1610
1651
attr[attrname]._dbus_annotations = dict(
1619
1660
# to the class.
1620
1661
attr[attrname] = (
1621
1662
dbus_interface_annotations(alt_interface)
1622
(types.FunctionType(attribute.func_code,
1623
attribute.func_globals,
1624
attribute.func_name,
1625
attribute.func_defaults,
1626
attribute.func_closure)))
1663
(copy_function(attribute)))
1628
1665
# Deprecate all alternate interfaces
1629
1666
iname="_AlternateDBusNames_interface_annotation{}"
1642
1679
if interface_names:
1643
1680
# Replace the class with a new subclass of it with
1644
1681
# methods, signals, etc. as created above.
1645
cls = type(b"{}Alternate".format(cls.__name__),
1682
if sys.version_info.major == 2:
1683
cls = type(b"{}Alternate".format(cls.__name__),
1686
cls = type("{}Alternate".format(cls.__name__),
1808
1849
def approve(self, value=True):
1809
1850
self.approved = value
1810
gobject.timeout_add(int(self.approval_duration.total_seconds()
1811
* 1000), self._reset_approved)
1851
GLib.timeout_add(int(self.approval_duration.total_seconds()
1852
* 1000), self._reset_approved)
1812
1853
self.send_changedstate()
1814
1855
## D-Bus methods, signals & properties
2024
2065
if (getattr(self, "disable_initiator_tag", None)
2027
gobject.source_remove(self.disable_initiator_tag)
2028
self.disable_initiator_tag = gobject.timeout_add(
2068
GLib.source_remove(self.disable_initiator_tag)
2069
self.disable_initiator_tag = GLib.timeout_add(
2029
2070
int((self.expires - now).total_seconds() * 1000),
2052
2093
if self.enabled:
2053
2094
# Reschedule checker run
2054
gobject.source_remove(self.checker_initiator_tag)
2055
self.checker_initiator_tag = gobject.timeout_add(
2095
GLib.source_remove(self.checker_initiator_tag)
2096
self.checker_initiator_tag = GLib.timeout_add(
2056
2097
value, self.start_checker)
2057
2098
self.start_checker() # Start one now, too
2150
2191
priority = self.server.gnutls_priority
2151
2192
if priority is None:
2152
2193
priority = "NORMAL"
2153
gnutls.priority_set_direct(session._c_object, priority,
2194
gnutls.priority_set_direct(session._c_object,
2195
priority.encode("utf-8"),
2156
2198
# Start communication using the Mandos protocol
2411
2453
"""This overrides the normal server_bind() function
2412
2454
to bind to an interface if one was specified, and also NOT to
2413
2455
bind to an address or port if they were not specified."""
2456
global SO_BINDTODEVICE
2414
2457
if self.interface is not None:
2415
2458
if SO_BINDTODEVICE is None:
2416
logger.error("SO_BINDTODEVICE does not exist;"
2417
" cannot bind to interface %s",
2421
self.socket.setsockopt(
2422
socket.SOL_SOCKET, SO_BINDTODEVICE,
2423
(self.interface + "\0").encode("utf-8"))
2424
except socket.error as error:
2425
if error.errno == errno.EPERM:
2426
logger.error("No permission to bind to"
2427
" interface %s", self.interface)
2428
elif error.errno == errno.ENOPROTOOPT:
2429
logger.error("SO_BINDTODEVICE not available;"
2430
" cannot bind to interface %s",
2432
elif error.errno == errno.ENODEV:
2433
logger.error("Interface %s does not exist,"
2434
" cannot bind", self.interface)
2459
# Fall back to a hard-coded value which seems to be
2461
logger.warning("SO_BINDTODEVICE not found, trying 25")
2462
SO_BINDTODEVICE = 25
2464
self.socket.setsockopt(
2465
socket.SOL_SOCKET, SO_BINDTODEVICE,
2466
(self.interface + "\0").encode("utf-8"))
2467
except socket.error as error:
2468
if error.errno == errno.EPERM:
2469
logger.error("No permission to bind to"
2470
" interface %s", self.interface)
2471
elif error.errno == errno.ENOPROTOOPT:
2472
logger.error("SO_BINDTODEVICE not available;"
2473
" cannot bind to interface %s",
2475
elif error.errno == errno.ENODEV:
2476
logger.error("Interface %s does not exist,"
2477
" cannot bind", self.interface)
2437
2480
# Only bind(2) the socket if we really need to.
2438
2481
if self.server_address[0] or self.server_address[1]:
2439
2482
if not self.server_address[0]:
2462
2505
gnutls_priority GnuTLS priority string
2463
2506
use_dbus: Boolean; to emit D-Bus signals or not
2465
Assumes a gobject.MainLoop event loop.
2508
Assumes a GLib.MainLoop event loop.
2468
2511
def __init__(self, server_address, RequestHandlerClass,
2494
2537
def add_pipe(self, parent_pipe, proc):
2495
2538
# Call "handle_ipc" for both data and EOF events
2496
gobject.io_add_watch(
2497
2540
parent_pipe.fileno(),
2498
gobject.IO_IN | gobject.IO_HUP,
2541
GLib.IO_IN | GLib.IO_HUP,
2499
2542
functools.partial(self.handle_ipc,
2500
2543
parent_pipe = parent_pipe,
2532
2575
parent_pipe.send(False)
2535
gobject.io_add_watch(
2536
2579
parent_pipe.fileno(),
2537
gobject.IO_IN | gobject.IO_HUP,
2580
GLib.IO_IN | GLib.IO_HUP,
2538
2581
functools.partial(self.handle_ipc,
2539
2582
parent_pipe = parent_pipe,
2922
2965
logger.error("Could not open file %r", pidfilename,
2925
for name in ("_mandos", "mandos", "nobody"):
2968
for name, group in (("_mandos", "_mandos"),
2969
("mandos", "mandos"),
2970
("nobody", "nogroup")):
2927
2972
uid = pwd.getpwnam(name).pw_uid
2928
gid = pwd.getpwnam(name).pw_gid
2973
gid = pwd.getpwnam(group).pw_gid
2930
2975
except KeyError:
2984
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2938
2986
except OSError as error:
2987
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2988
.format(uid, gid, os.strerror(error.errno)))
2939
2989
if error.errno != errno.EPERM:
2963
3013
# Close all input and output, do double fork, etc.
2966
# multiprocessing will use threads, so before we use gobject we
2967
# need to inform gobject that threads will be used.
2968
gobject.threads_init()
3016
# multiprocessing will use threads, so before we use GLib we need
3017
# to inform GLib that threads will be used.
2970
3020
global main_loop
2971
3021
# From the Avahi example code
2972
3022
DBusGMainLoop(set_as_default=True)
2973
main_loop = gobject.MainLoop()
3023
main_loop = GLib.MainLoop()
2974
3024
bus = dbus.SystemBus()
2975
3025
# End of Avahi example code
3020
3070
if server_settings["restore"]:
3022
3072
with open(stored_state_path, "rb") as stored_state:
3023
clients_data, old_client_settings = pickle.load(
3073
if sys.version_info.major == 2:
3074
clients_data, old_client_settings = pickle.load(
3077
bytes_clients_data, bytes_old_client_settings = (
3078
pickle.load(stored_state, encoding = "bytes"))
3079
### Fix bytes to strings
3082
clients_data = { (key.decode("utf-8")
3083
if isinstance(key, bytes)
3086
bytes_clients_data.items() }
3087
del bytes_clients_data
3088
for key in clients_data:
3089
value = { (k.decode("utf-8")
3090
if isinstance(k, bytes) else k): v
3092
clients_data[key].items() }
3093
clients_data[key] = value
3095
value["client_structure"] = [
3097
if isinstance(s, bytes)
3099
value["client_structure"] ]
3101
for k in ("name", "host"):
3102
if isinstance(value[k], bytes):
3103
value[k] = value[k].decode("utf-8")
3104
## old_client_settings
3106
old_client_settings = {
3107
(key.decode("utf-8")
3108
if isinstance(key, bytes)
3111
bytes_old_client_settings.items() }
3112
del bytes_old_client_settings
3114
for value in old_client_settings.values():
3115
if isinstance(value["host"], bytes):
3116
value["host"] = (value["host"]
3025
3118
os.remove(stored_state_path)
3026
3119
except IOError as e:
3027
3120
if e.errno == errno.ENOENT:
3129
3222
del pidfilename
3131
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3132
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3224
for termsig in (signal.SIGHUP, signal.SIGTERM):
3225
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3226
lambda: main_loop.quit() and False)
3177
3271
return dbus.Dictionary(
3178
3272
{ c.dbus_object_path: c.GetAll(
3179
3273
"se.recompile.Mandos.Client")
3180
for c in tcp_server.clients.itervalues() },
3274
for c in tcp_server.clients.values() },
3181
3275
signature="oa{sv}")
3183
3277
@dbus.service.method(_interface, in_signature="o")
3184
3278
def RemoveClient(self, object_path):
3186
for c in tcp_server.clients.itervalues():
3280
for c in tcp_server.clients.values():
3187
3281
if c.dbus_object_path == object_path:
3188
3282
del tcp_server.clients[c.name]
3189
3283
c.remove_from_connection()
3235
3329
mandos_dbus_service = MandosDBusService()
3331
# Save modules to variables to exempt the modules from being
3332
# unloaded before the function registered with atexit() is run.
3333
mp = multiprocessing
3238
3336
"Cleanup function; run on exit"
3240
3338
service.cleanup()
3242
multiprocessing.active_children()
3340
mp.active_children()
3244
3342
if not (tcp_server.clients or client_settings):
3249
3347
# removed/edited, old secret will thus be unrecovable.
3251
3349
with PGPEngine() as pgp:
3252
for client in tcp_server.clients.itervalues():
3350
for client in tcp_server.clients.values():
3253
3351
key = client_settings[client.name]["secret"]
3254
3352
client.encrypted_secret = pgp.encrypt(client.secret,
3279
3377
prefix='clients-',
3280
3378
dir=os.path.dirname(stored_state_path),
3281
3379
delete=False) as stored_state:
3282
pickle.dump((clients, client_settings), stored_state)
3380
pickle.dump((clients, client_settings), stored_state,
3283
3382
tempname = stored_state.name
3284
3383
os.rename(tempname, stored_state_path)
3285
3384
except (IOError, OSError) as e:
3346
3445
# End of Avahi example code
3348
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3349
lambda *args, **kwargs:
3350
(tcp_server.handle_request
3351
(*args[2:], **kwargs) or True))
3447
GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
3448
lambda *args, **kwargs:
3449
(tcp_server.handle_request
3450
(*args[2:], **kwargs) or True))
3353
3452
logger.debug("Starting main loop")
3354
3453
main_loop.run()