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
2413
2455
bind to an address or port if they were not specified."""
2414
2456
if self.interface is not None:
2415
2457
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)
2458
# Fall back to a hard-coded value which seems to be
2460
logger.warning("SO_BINDTODEVICE not found, trying 25")
2461
SO_BINDTODEVICE = 25
2463
self.socket.setsockopt(
2464
socket.SOL_SOCKET, SO_BINDTODEVICE,
2465
(self.interface + "\0").encode("utf-8"))
2466
except socket.error as error:
2467
if error.errno == errno.EPERM:
2468
logger.error("No permission to bind to"
2469
" interface %s", self.interface)
2470
elif error.errno == errno.ENOPROTOOPT:
2471
logger.error("SO_BINDTODEVICE not available;"
2472
" cannot bind to interface %s",
2474
elif error.errno == errno.ENODEV:
2475
logger.error("Interface %s does not exist,"
2476
" cannot bind", self.interface)
2437
2479
# Only bind(2) the socket if we really need to.
2438
2480
if self.server_address[0] or self.server_address[1]:
2439
2481
if not self.server_address[0]:
2462
2504
gnutls_priority GnuTLS priority string
2463
2505
use_dbus: Boolean; to emit D-Bus signals or not
2465
Assumes a gobject.MainLoop event loop.
2507
Assumes a GLib.MainLoop event loop.
2468
2510
def __init__(self, server_address, RequestHandlerClass,
2494
2536
def add_pipe(self, parent_pipe, proc):
2495
2537
# Call "handle_ipc" for both data and EOF events
2496
gobject.io_add_watch(
2497
2539
parent_pipe.fileno(),
2498
gobject.IO_IN | gobject.IO_HUP,
2540
GLib.IO_IN | GLib.IO_HUP,
2499
2541
functools.partial(self.handle_ipc,
2500
2542
parent_pipe = parent_pipe,
2532
2574
parent_pipe.send(False)
2535
gobject.io_add_watch(
2536
2578
parent_pipe.fileno(),
2537
gobject.IO_IN | gobject.IO_HUP,
2579
GLib.IO_IN | GLib.IO_HUP,
2538
2580
functools.partial(self.handle_ipc,
2539
2581
parent_pipe = parent_pipe,
2922
2964
logger.error("Could not open file %r", pidfilename,
2925
for name in ("_mandos", "mandos", "nobody"):
2967
for name, group in (("_mandos", "_mandos"),
2968
("mandos", "mandos"),
2969
("nobody", "nogroup")):
2927
2971
uid = pwd.getpwnam(name).pw_uid
2928
gid = pwd.getpwnam(name).pw_gid
2972
gid = pwd.getpwnam(group).pw_gid
2930
2974
except KeyError:
2983
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2938
2985
except OSError as error:
2986
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2987
.format(uid, gid, os.strerror(error.errno)))
2939
2988
if error.errno != errno.EPERM:
2963
3012
# 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()
3015
# multiprocessing will use threads, so before we use GLib we need
3016
# to inform GLib that threads will be used.
2970
3019
global main_loop
2971
3020
# From the Avahi example code
2972
3021
DBusGMainLoop(set_as_default=True)
2973
main_loop = gobject.MainLoop()
3022
main_loop = GLib.MainLoop()
2974
3023
bus = dbus.SystemBus()
2975
3024
# End of Avahi example code
3020
3069
if server_settings["restore"]:
3022
3071
with open(stored_state_path, "rb") as stored_state:
3023
clients_data, old_client_settings = pickle.load(
3072
if sys.version_info.major == 2:
3073
clients_data, old_client_settings = pickle.load(
3076
bytes_clients_data, bytes_old_client_settings = (
3077
pickle.load(stored_state, encoding = "bytes"))
3078
### Fix bytes to strings
3081
clients_data = { (key.decode("utf-8")
3082
if isinstance(key, bytes)
3085
bytes_clients_data.items() }
3086
del bytes_clients_data
3087
for key in clients_data:
3088
value = { (k.decode("utf-8")
3089
if isinstance(k, bytes) else k): v
3091
clients_data[key].items() }
3092
clients_data[key] = value
3094
value["client_structure"] = [
3096
if isinstance(s, bytes)
3098
value["client_structure"] ]
3100
for k in ("name", "host"):
3101
if isinstance(value[k], bytes):
3102
value[k] = value[k].decode("utf-8")
3103
## old_client_settings
3105
old_client_settings = {
3106
(key.decode("utf-8")
3107
if isinstance(key, bytes)
3110
bytes_old_client_settings.items() }
3111
del bytes_old_client_settings
3113
for value in old_client_settings.values():
3114
if isinstance(value["host"], bytes):
3115
value["host"] = (value["host"]
3025
3117
os.remove(stored_state_path)
3026
3118
except IOError as e:
3027
3119
if e.errno == errno.ENOENT:
3129
3221
del pidfilename
3131
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3132
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3223
for termsig in (signal.SIGHUP, signal.SIGTERM):
3224
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3225
lambda: main_loop.quit() and False)
3177
3270
return dbus.Dictionary(
3178
3271
{ c.dbus_object_path: c.GetAll(
3179
3272
"se.recompile.Mandos.Client")
3180
for c in tcp_server.clients.itervalues() },
3273
for c in tcp_server.clients.values() },
3181
3274
signature="oa{sv}")
3183
3276
@dbus.service.method(_interface, in_signature="o")
3184
3277
def RemoveClient(self, object_path):
3186
for c in tcp_server.clients.itervalues():
3279
for c in tcp_server.clients.values():
3187
3280
if c.dbus_object_path == object_path:
3188
3281
del tcp_server.clients[c.name]
3189
3282
c.remove_from_connection()
3235
3328
mandos_dbus_service = MandosDBusService()
3330
# Save modules to variables to exempt the modules from being
3331
# unloaded before the function registered with atexit() is run.
3332
mp = multiprocessing
3238
3335
"Cleanup function; run on exit"
3240
3337
service.cleanup()
3242
multiprocessing.active_children()
3339
mp.active_children()
3244
3341
if not (tcp_server.clients or client_settings):
3249
3346
# removed/edited, old secret will thus be unrecovable.
3251
3348
with PGPEngine() as pgp:
3252
for client in tcp_server.clients.itervalues():
3349
for client in tcp_server.clients.values():
3253
3350
key = client_settings[client.name]["secret"]
3254
3351
client.encrypted_secret = pgp.encrypt(client.secret,
3279
3376
prefix='clients-',
3280
3377
dir=os.path.dirname(stored_state_path),
3281
3378
delete=False) as stored_state:
3282
pickle.dump((clients, client_settings), stored_state)
3379
pickle.dump((clients, client_settings), stored_state,
3283
3381
tempname = stored_state.name
3284
3382
os.rename(tempname, stored_state_path)
3285
3383
except (IOError, OSError) as e:
3346
3444
# 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))
3446
GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
3447
lambda *args, **kwargs:
3448
(tcp_server.handle_request
3449
(*args[2:], **kwargs) or True))
3353
3451
logger.debug("Starting main loop")
3354
3452
main_loop.run()