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
82
from gi.repository 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):
545
487
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
546
488
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
547
489
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
548
credentials_type_t = ctypes.c_int
490
credentials_type_t = ctypes.c_int #
549
491
transport_ptr_t = ctypes.c_void_p
550
492
close_request_t = ctypes.c_int
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
2455
2413
bind to an address or port if they were not specified."""
2456
2414
if self.interface is not None:
2457
2415
if SO_BINDTODEVICE is None:
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)
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)
2479
2437
# Only bind(2) the socket if we really need to.
2480
2438
if self.server_address[0] or self.server_address[1]:
2481
2439
if not self.server_address[0]:
2504
2462
gnutls_priority GnuTLS priority string
2505
2463
use_dbus: Boolean; to emit D-Bus signals or not
2507
Assumes a GLib.MainLoop event loop.
2465
Assumes a gobject.MainLoop event loop.
2510
2468
def __init__(self, server_address, RequestHandlerClass,
2536
2494
def add_pipe(self, parent_pipe, proc):
2537
2495
# Call "handle_ipc" for both data and EOF events
2496
gobject.io_add_watch(
2539
2497
parent_pipe.fileno(),
2540
GLib.IO_IN | GLib.IO_HUP,
2498
gobject.IO_IN | gobject.IO_HUP,
2541
2499
functools.partial(self.handle_ipc,
2542
2500
parent_pipe = parent_pipe,
2574
2532
parent_pipe.send(False)
2535
gobject.io_add_watch(
2578
2536
parent_pipe.fileno(),
2579
GLib.IO_IN | GLib.IO_HUP,
2537
gobject.IO_IN | gobject.IO_HUP,
2580
2538
functools.partial(self.handle_ipc,
2581
2539
parent_pipe = parent_pipe,
2983
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2985
2940
except OSError as error:
2986
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2987
.format(uid, gid, os.strerror(error.errno)))
2988
2941
if error.errno != errno.EPERM:
3012
2965
# Close all input and output, do double fork, etc.
3015
# multiprocessing will use threads, so before we use GLib we need
3016
# 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()
3019
2972
global main_loop
3020
2973
# From the Avahi example code
3021
2974
DBusGMainLoop(set_as_default=True)
3022
main_loop = GLib.MainLoop()
2975
main_loop = gobject.MainLoop()
3023
2976
bus = dbus.SystemBus()
3024
2977
# End of Avahi example code
3069
3022
if server_settings["restore"]:
3071
3024
with open(stored_state_path, "rb") as stored_state:
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
clients_data, old_client_settings = pickle.load(
3117
3027
os.remove(stored_state_path)
3118
3028
except IOError as e:
3119
3029
if e.errno == errno.ENOENT:
3221
3131
del pidfilename
3223
for termsig in (signal.SIGHUP, signal.SIGTERM):
3224
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3225
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())
3270
3179
return dbus.Dictionary(
3271
3180
{ c.dbus_object_path: c.GetAll(
3272
3181
"se.recompile.Mandos.Client")
3273
for c in tcp_server.clients.values() },
3182
for c in tcp_server.clients.itervalues() },
3274
3183
signature="oa{sv}")
3276
3185
@dbus.service.method(_interface, in_signature="o")
3277
3186
def RemoveClient(self, object_path):
3279
for c in tcp_server.clients.values():
3188
for c in tcp_server.clients.itervalues():
3280
3189
if c.dbus_object_path == object_path:
3281
3190
del tcp_server.clients[c.name]
3282
3191
c.remove_from_connection()
3328
3237
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
3335
3240
"Cleanup function; run on exit"
3337
3242
service.cleanup()
3339
mp.active_children()
3244
multiprocessing.active_children()
3341
3246
if not (tcp_server.clients or client_settings):
3346
3251
# removed/edited, old secret will thus be unrecovable.
3348
3253
with PGPEngine() as pgp:
3349
for client in tcp_server.clients.values():
3254
for client in tcp_server.clients.itervalues():
3350
3255
key = client_settings[client.name]["secret"]
3351
3256
client.encrypted_secret = pgp.encrypt(client.secret,
3376
3281
prefix='clients-',
3377
3282
dir=os.path.dirname(stored_state_path),
3378
3283
delete=False) as stored_state:
3379
pickle.dump((clients, client_settings), stored_state,
3284
pickle.dump((clients, client_settings), stored_state)
3381
3285
tempname = stored_state.name
3382
3286
os.rename(tempname, stored_state_path)
3383
3287
except (IOError, OSError) as e:
3444
3348
# End of Avahi example code
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))
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))
3451
3355
logger.debug("Starting main loop")
3452
3356
main_loop.run()