34
34
from __future__ import (division, absolute_import, print_function,
38
from future_builtins import *
37
from future_builtins import *
43
40
import SocketServer as socketserver
81
78
import dbus.service
82
from gi.repository import GLib
80
from gi.repository import GObject
82
import gobject as GObject
83
84
from dbus.mainloop.glib import DBusGMainLoop
86
87
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:
93
91
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
94
92
except AttributeError:
96
# This is where SO_BINDTODEVICE was up to and including Python
98
94
from IN import SO_BINDTODEVICE
99
95
except ImportError:
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
96
SO_BINDTODEVICE = None
114
98
if sys.version_info.major == 2:
118
102
stored_state_file = "clients.pickle"
120
104
logger = logging.getLogger()
135
119
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__,
154
122
def initlogger(debug, level=logging.WARNING):
155
123
"""init logger and add loglevel"""
188
156
output = subprocess.check_output(["gpgconf"])
189
157
for line in output.splitlines():
190
name, text, path = line.split(b":")
158
name, text, path = line.split(":")
191
159
if name == "gpg":
197
165
self.gnupgargs = ['--batch',
198
166
'--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")
205
171
def __enter__(self):
272
238
raise PGPError(err)
273
239
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
300
242
class AvahiError(Exception):
301
243
def __init__(self, value, *args, **kwargs):
773
715
checker: subprocess.Popen(); a running checker process used
774
716
to see if the client lives.
775
717
'None' if no process is running.
776
checker_callback_tag: a GLib event source tag, or None
718
checker_callback_tag: a GObject event source tag, or None
777
719
checker_command: string; External command which is run to check
778
720
if client lives. %() expansions are done at
779
721
runtime with vars(self) as dict, so that for
780
722
instance %(name)s can be used in the command.
781
checker_initiator_tag: a GLib event source tag, or None
723
checker_initiator_tag: a GObject event source tag, or None
782
724
created: datetime.datetime(); (UTC) object creation
783
725
client_structure: Object describing what attributes a client has
784
726
and is used for storing the client at exit
785
727
current_checker_command: string; current running checker_command
786
disable_initiator_tag: a GLib event source tag, or None
728
disable_initiator_tag: a GObject event source tag, or None
788
730
fingerprint: string (40 or 32 hexadecimal digits); used to
789
731
uniquely identify the client
852
794
client["fingerprint"] = (section["fingerprint"].upper()
853
795
.replace(" ", ""))
854
796
if "secret" in section:
855
client["secret"] = codecs.decode(section["secret"]
797
client["secret"] = section["secret"].decode("base64")
858
798
elif "secfile" in section:
859
799
with open(os.path.expanduser(os.path.expandvars
860
800
(section["secfile"])),
913
853
self.changedstate = multiprocessing_manager.Condition(
914
854
multiprocessing_manager.Lock())
915
855
self.client_structure = [attr
916
for attr in self.__dict__.keys()
856
for attr in self.__dict__.iterkeys()
917
857
if not attr.startswith("_")]
918
858
self.client_structure.append("client_structure")
946
886
logger.info("Disabling client %s", self.name)
947
887
if getattr(self, "disable_initiator_tag", None) is not None:
948
GLib.source_remove(self.disable_initiator_tag)
888
GObject.source_remove(self.disable_initiator_tag)
949
889
self.disable_initiator_tag = None
950
890
self.expires = None
951
891
if getattr(self, "checker_initiator_tag", None) is not None:
952
GLib.source_remove(self.checker_initiator_tag)
892
GObject.source_remove(self.checker_initiator_tag)
953
893
self.checker_initiator_tag = None
954
894
self.stop_checker()
955
895
self.enabled = False
957
897
self.send_changedstate()
958
# Do not run this again if called by a GLib.timeout_add
898
# Do not run this again if called by a GObject.timeout_add
961
901
def __del__(self):
965
905
# Schedule a new checker to be started an 'interval' from now,
966
906
# and every interval from then on.
967
907
if self.checker_initiator_tag is not None:
968
GLib.source_remove(self.checker_initiator_tag)
969
self.checker_initiator_tag = GLib.timeout_add(
908
GObject.source_remove(self.checker_initiator_tag)
909
self.checker_initiator_tag = GObject.timeout_add(
970
910
int(self.interval.total_seconds() * 1000),
971
911
self.start_checker)
972
912
# Schedule a disable() when 'timeout' has passed
973
913
if self.disable_initiator_tag is not None:
974
GLib.source_remove(self.disable_initiator_tag)
975
self.disable_initiator_tag = GLib.timeout_add(
914
GObject.source_remove(self.disable_initiator_tag)
915
self.disable_initiator_tag = GObject.timeout_add(
976
916
int(self.timeout.total_seconds() * 1000), self.disable)
977
917
# Also start a new checker *right now*.
978
918
self.start_checker()
1014
954
if timeout is None:
1015
955
timeout = self.timeout
1016
956
if self.disable_initiator_tag is not None:
1017
GLib.source_remove(self.disable_initiator_tag)
957
GObject.source_remove(self.disable_initiator_tag)
1018
958
self.disable_initiator_tag = None
1019
959
if getattr(self, "enabled", False):
1020
self.disable_initiator_tag = GLib.timeout_add(
960
self.disable_initiator_tag = GObject.timeout_add(
1021
961
int(timeout.total_seconds() * 1000), self.disable)
1022
962
self.expires = datetime.datetime.utcnow() + timeout
1078
1018
args = (pipe[1], subprocess.call, command),
1079
1019
kwargs = popen_args)
1080
1020
self.checker.start()
1081
self.checker_callback_tag = GLib.io_add_watch(
1082
pipe[0].fileno(), GLib.IO_IN,
1021
self.checker_callback_tag = GObject.io_add_watch(
1022
pipe[0].fileno(), GObject.IO_IN,
1083
1023
self.checker_callback, pipe[0], command)
1084
# Re-run this periodically if run by GLib.timeout_add
1024
# Re-run this periodically if run by GObject.timeout_add
1087
1027
def stop_checker(self):
1088
1028
"""Force the checker process, if any, to stop."""
1089
1029
if self.checker_callback_tag:
1090
GLib.source_remove(self.checker_callback_tag)
1030
GObject.source_remove(self.checker_callback_tag)
1091
1031
self.checker_callback_tag = None
1092
1032
if getattr(self, "checker", None) is None:
1567
1507
interface_names.add(alt_interface)
1568
1508
# Is this a D-Bus signal?
1569
1509
if getattr(attribute, "_dbus_is_signal", False):
1570
# Extract the original non-method undecorated
1571
# function by black magic
1572
1510
if sys.version_info.major == 2:
1511
# Extract the original non-method undecorated
1512
# function by black magic
1573
1513
nonmethod_func = (dict(
1574
1514
zip(attribute.func_code.co_freevars,
1575
1515
attribute.__closure__))
1576
1516
["func"].cell_contents)
1578
nonmethod_func = (dict(
1579
zip(attribute.__code__.co_freevars,
1580
attribute.__closure__))
1581
["func"].cell_contents)
1518
nonmethod_func = attribute
1582
1519
# Create a new, but exactly alike, function
1583
1520
# object, and decorate it to be a new D-Bus signal
1584
1521
# with the alternate D-Bus interface name
1585
new_function = copy_function(nonmethod_func)
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__)
1586
1536
new_function = (dbus.service.signal(
1588
1538
attribute._dbus_signature)(new_function))
1628
1578
attribute._dbus_in_signature,
1629
1579
attribute._dbus_out_signature)
1630
(copy_function(attribute)))
1580
(types.FunctionType(attribute.func_code,
1581
attribute.func_globals,
1582
attribute.func_name,
1583
attribute.func_defaults,
1584
attribute.func_closure)))
1631
1585
# Copy annotations, if any
1633
1587
attr[attrname]._dbus_annotations = dict(
1645
1599
attribute._dbus_access,
1646
1600
attribute._dbus_get_args_options
1647
1601
["byte_arrays"])
1648
(copy_function(attribute)))
1602
(types.FunctionType(
1603
attribute.func_code,
1604
attribute.func_globals,
1605
attribute.func_name,
1606
attribute.func_defaults,
1607
attribute.func_closure)))
1649
1608
# Copy annotations, if any
1651
1610
attr[attrname]._dbus_annotations = dict(
1660
1619
# to the class.
1661
1620
attr[attrname] = (
1662
1621
dbus_interface_annotations(alt_interface)
1663
(copy_function(attribute)))
1622
(types.FunctionType(attribute.func_code,
1623
attribute.func_globals,
1624
attribute.func_name,
1625
attribute.func_defaults,
1626
attribute.func_closure)))
1665
1628
# Deprecate all alternate interfaces
1666
1629
iname="_AlternateDBusNames_interface_annotation{}"
1679
1642
if interface_names:
1680
1643
# Replace the class with a new subclass of it with
1681
1644
# methods, signals, etc. as created above.
1682
if sys.version_info.major == 2:
1683
cls = type(b"{}Alternate".format(cls.__name__),
1686
cls = type("{}Alternate".format(cls.__name__),
1645
cls = type(b"{}Alternate".format(cls.__name__),
1849
1808
def approve(self, value=True):
1850
1809
self.approved = value
1851
GLib.timeout_add(int(self.approval_duration.total_seconds()
1852
* 1000), self._reset_approved)
1810
GObject.timeout_add(int(self.approval_duration.total_seconds()
1811
* 1000), self._reset_approved)
1853
1812
self.send_changedstate()
1855
1814
## D-Bus methods, signals & properties
2065
2024
if (getattr(self, "disable_initiator_tag", None)
2068
GLib.source_remove(self.disable_initiator_tag)
2069
self.disable_initiator_tag = GLib.timeout_add(
2027
GObject.source_remove(self.disable_initiator_tag)
2028
self.disable_initiator_tag = GObject.timeout_add(
2070
2029
int((self.expires - now).total_seconds() * 1000),
2093
2052
if self.enabled:
2094
2053
# Reschedule checker run
2095
GLib.source_remove(self.checker_initiator_tag)
2096
self.checker_initiator_tag = GLib.timeout_add(
2054
GObject.source_remove(self.checker_initiator_tag)
2055
self.checker_initiator_tag = GObject.timeout_add(
2097
2056
value, self.start_checker)
2098
2057
self.start_checker() # Start one now, too
2191
2150
priority = self.server.gnutls_priority
2192
2151
if priority is None:
2193
2152
priority = "NORMAL"
2194
gnutls.priority_set_direct(session._c_object,
2195
priority.encode("utf-8"),
2153
gnutls.priority_set_direct(session._c_object, priority,
2198
2156
# Start communication using the Mandos protocol
2453
2411
"""This overrides the normal server_bind() function
2454
2412
to bind to an interface if one was specified, and also NOT to
2455
2413
bind to an address or port if they were not specified."""
2456
global SO_BINDTODEVICE
2457
2414
if self.interface is not None:
2458
2415
if SO_BINDTODEVICE is None:
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)
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)
2480
2437
# Only bind(2) the socket if we really need to.
2481
2438
if self.server_address[0] or self.server_address[1]:
2482
2439
if not self.server_address[0]:
2505
2462
gnutls_priority GnuTLS priority string
2506
2463
use_dbus: Boolean; to emit D-Bus signals or not
2508
Assumes a GLib.MainLoop event loop.
2465
Assumes a GObject.MainLoop event loop.
2511
2468
def __init__(self, server_address, RequestHandlerClass,
2537
2494
def add_pipe(self, parent_pipe, proc):
2538
2495
# Call "handle_ipc" for both data and EOF events
2496
GObject.io_add_watch(
2540
2497
parent_pipe.fileno(),
2541
GLib.IO_IN | GLib.IO_HUP,
2498
GObject.IO_IN | GObject.IO_HUP,
2542
2499
functools.partial(self.handle_ipc,
2543
2500
parent_pipe = parent_pipe,
2575
2532
parent_pipe.send(False)
2535
GObject.io_add_watch(
2579
2536
parent_pipe.fileno(),
2580
GLib.IO_IN | GLib.IO_HUP,
2537
GObject.IO_IN | GObject.IO_HUP,
2581
2538
functools.partial(self.handle_ipc,
2582
2539
parent_pipe = parent_pipe,
2984
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2986
2940
except OSError as error:
2987
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2988
.format(uid, gid, os.strerror(error.errno)))
2989
2941
if error.errno != errno.EPERM:
3013
2965
# Close all input and output, do double fork, etc.
3016
# multiprocessing will use threads, so before we use GLib we need
3017
# to inform GLib that threads will be used.
2968
# multiprocessing will use threads, so before we use GObject we
2969
# need to inform GObject that threads will be used.
2970
GObject.threads_init()
3020
2972
global main_loop
3021
2973
# From the Avahi example code
3022
2974
DBusGMainLoop(set_as_default=True)
3023
main_loop = GLib.MainLoop()
2975
main_loop = GObject.MainLoop()
3024
2976
bus = dbus.SystemBus()
3025
2977
# End of Avahi example code
3070
3022
if server_settings["restore"]:
3072
3024
with open(stored_state_path, "rb") as stored_state:
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
clients_data, old_client_settings = pickle.load(
3118
3027
os.remove(stored_state_path)
3119
3028
except IOError as e:
3120
3029
if e.errno == errno.ENOENT:
3222
3131
del pidfilename
3224
for termsig in (signal.SIGHUP, signal.SIGTERM):
3225
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3226
lambda: main_loop.quit() and False)
3133
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3134
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3271
3179
return dbus.Dictionary(
3272
3180
{ c.dbus_object_path: c.GetAll(
3273
3181
"se.recompile.Mandos.Client")
3274
for c in tcp_server.clients.values() },
3182
for c in tcp_server.clients.itervalues() },
3275
3183
signature="oa{sv}")
3277
3185
@dbus.service.method(_interface, in_signature="o")
3278
3186
def RemoveClient(self, object_path):
3280
for c in tcp_server.clients.values():
3188
for c in tcp_server.clients.itervalues():
3281
3189
if c.dbus_object_path == object_path:
3282
3190
del tcp_server.clients[c.name]
3283
3191
c.remove_from_connection()
3329
3237
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
3336
3240
"Cleanup function; run on exit"
3338
3242
service.cleanup()
3340
mp.active_children()
3244
multiprocessing.active_children()
3342
3246
if not (tcp_server.clients or client_settings):
3347
3251
# removed/edited, old secret will thus be unrecovable.
3349
3253
with PGPEngine() as pgp:
3350
for client in tcp_server.clients.values():
3254
for client in tcp_server.clients.itervalues():
3351
3255
key = client_settings[client.name]["secret"]
3352
3256
client.encrypted_secret = pgp.encrypt(client.secret,
3377
3281
prefix='clients-',
3378
3282
dir=os.path.dirname(stored_state_path),
3379
3283
delete=False) as stored_state:
3380
pickle.dump((clients, client_settings), stored_state,
3284
pickle.dump((clients, client_settings), stored_state)
3382
3285
tempname = stored_state.name
3383
3286
os.rename(tempname, stored_state_path)
3384
3287
except (IOError, OSError) as e:
3445
3348
# End of Avahi example code
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))
3350
GObject.io_add_watch(tcp_server.fileno(), GObject.IO_IN,
3351
lambda *args, **kwargs:
3352
(tcp_server.handle_request
3353
(*args[2:], **kwargs) or True))
3452
3355
logger.debug("Starting main loop")
3453
3356
main_loop.run()