11
11
# "AvahiService" class, and some lines in "main".
13
13
# Everything else is
14
# Copyright © 2008-2016 Teddy Hogeborn
15
# Copyright © 2008-2016 Björn Påhlsson
14
# Copyright © 2008-2015 Teddy Hogeborn
15
# Copyright © 2008-2015 Björn Påhlsson
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
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"""
184
152
def __init__(self):
185
153
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
188
output = subprocess.check_output(["gpgconf"])
189
for line in output.splitlines():
190
name, text, path = line.split(b":")
195
if e.errno != errno.ENOENT:
197
154
self.gnupgargs = ['--batch',
198
'--homedir', self.tempdir,
155
'--home', 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
160
def __enter__(self):
242
197
dir=self.tempdir) as passfile:
243
198
passfile.write(passphrase)
245
proc = subprocess.Popen([self.gpg, '--symmetric',
200
proc = subprocess.Popen(['gpg', '--symmetric',
246
201
'--passphrase-file',
248
203
+ self.gnupgargs,
260
215
dir = self.tempdir) as passfile:
261
216
passfile.write(passphrase)
263
proc = subprocess.Popen([self.gpg, '--decrypt',
218
proc = subprocess.Popen(['gpg', '--decrypt',
264
219
'--passphrase-file',
266
221
+ self.gnupgargs,
272
227
raise PGPError(err)
273
228
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
231
class AvahiError(Exception):
301
232
def __init__(self, value, *args, **kwargs):
545
476
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
546
477
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
547
478
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
548
credentials_type_t = ctypes.c_int
479
credentials_type_t = ctypes.c_int #
549
480
transport_ptr_t = ctypes.c_void_p
550
481
close_request_t = ctypes.c_int
773
704
checker: subprocess.Popen(); a running checker process used
774
705
to see if the client lives.
775
706
'None' if no process is running.
776
checker_callback_tag: a GLib event source tag, or None
707
checker_callback_tag: a gobject event source tag, or None
777
708
checker_command: string; External command which is run to check
778
709
if client lives. %() expansions are done at
779
710
runtime with vars(self) as dict, so that for
780
711
instance %(name)s can be used in the command.
781
checker_initiator_tag: a GLib event source tag, or None
712
checker_initiator_tag: a gobject event source tag, or None
782
713
created: datetime.datetime(); (UTC) object creation
783
714
client_structure: Object describing what attributes a client has
784
715
and is used for storing the client at exit
785
716
current_checker_command: string; current running checker_command
786
disable_initiator_tag: a GLib event source tag, or None
717
disable_initiator_tag: a gobject event source tag, or None
788
719
fingerprint: string (40 or 32 hexadecimal digits); used to
789
720
uniquely identify the client
852
783
client["fingerprint"] = (section["fingerprint"].upper()
853
784
.replace(" ", ""))
854
785
if "secret" in section:
855
client["secret"] = codecs.decode(section["secret"]
786
client["secret"] = section["secret"].decode("base64")
858
787
elif "secfile" in section:
859
788
with open(os.path.expanduser(os.path.expandvars
860
789
(section["secfile"])),
913
842
self.changedstate = multiprocessing_manager.Condition(
914
843
multiprocessing_manager.Lock())
915
844
self.client_structure = [attr
916
for attr in self.__dict__.keys()
845
for attr in self.__dict__.iterkeys()
917
846
if not attr.startswith("_")]
918
847
self.client_structure.append("client_structure")
946
875
logger.info("Disabling client %s", self.name)
947
876
if getattr(self, "disable_initiator_tag", None) is not None:
948
GLib.source_remove(self.disable_initiator_tag)
877
gobject.source_remove(self.disable_initiator_tag)
949
878
self.disable_initiator_tag = None
950
879
self.expires = None
951
880
if getattr(self, "checker_initiator_tag", None) is not None:
952
GLib.source_remove(self.checker_initiator_tag)
881
gobject.source_remove(self.checker_initiator_tag)
953
882
self.checker_initiator_tag = None
954
883
self.stop_checker()
955
884
self.enabled = False
957
886
self.send_changedstate()
958
# Do not run this again if called by a GLib.timeout_add
887
# Do not run this again if called by a gobject.timeout_add
961
890
def __del__(self):
965
894
# Schedule a new checker to be started an 'interval' from now,
966
895
# and every interval from then on.
967
896
if self.checker_initiator_tag is not None:
968
GLib.source_remove(self.checker_initiator_tag)
969
self.checker_initiator_tag = GLib.timeout_add(
897
gobject.source_remove(self.checker_initiator_tag)
898
self.checker_initiator_tag = gobject.timeout_add(
970
899
int(self.interval.total_seconds() * 1000),
971
900
self.start_checker)
972
901
# Schedule a disable() when 'timeout' has passed
973
902
if self.disable_initiator_tag is not None:
974
GLib.source_remove(self.disable_initiator_tag)
975
self.disable_initiator_tag = GLib.timeout_add(
903
gobject.source_remove(self.disable_initiator_tag)
904
self.disable_initiator_tag = gobject.timeout_add(
976
905
int(self.timeout.total_seconds() * 1000), self.disable)
977
906
# Also start a new checker *right now*.
978
907
self.start_checker()
1014
943
if timeout is None:
1015
944
timeout = self.timeout
1016
945
if self.disable_initiator_tag is not None:
1017
GLib.source_remove(self.disable_initiator_tag)
946
gobject.source_remove(self.disable_initiator_tag)
1018
947
self.disable_initiator_tag = None
1019
948
if getattr(self, "enabled", False):
1020
self.disable_initiator_tag = GLib.timeout_add(
949
self.disable_initiator_tag = gobject.timeout_add(
1021
950
int(timeout.total_seconds() * 1000), self.disable)
1022
951
self.expires = datetime.datetime.utcnow() + timeout
1078
1007
args = (pipe[1], subprocess.call, command),
1079
1008
kwargs = popen_args)
1080
1009
self.checker.start()
1081
self.checker_callback_tag = GLib.io_add_watch(
1082
pipe[0].fileno(), GLib.IO_IN,
1010
self.checker_callback_tag = gobject.io_add_watch(
1011
pipe[0].fileno(), gobject.IO_IN,
1083
1012
self.checker_callback, pipe[0], command)
1084
# Re-run this periodically if run by GLib.timeout_add
1013
# Re-run this periodically if run by gobject.timeout_add
1087
1016
def stop_checker(self):
1088
1017
"""Force the checker process, if any, to stop."""
1089
1018
if self.checker_callback_tag:
1090
GLib.source_remove(self.checker_callback_tag)
1019
gobject.source_remove(self.checker_callback_tag)
1091
1020
self.checker_callback_tag = None
1092
1021
if getattr(self, "checker", None) is None:
1567
1496
interface_names.add(alt_interface)
1568
1497
# Is this a D-Bus signal?
1569
1498
if getattr(attribute, "_dbus_is_signal", False):
1570
# Extract the original non-method undecorated
1571
# function by black magic
1572
1499
if sys.version_info.major == 2:
1500
# Extract the original non-method undecorated
1501
# function by black magic
1573
1502
nonmethod_func = (dict(
1574
1503
zip(attribute.func_code.co_freevars,
1575
1504
attribute.__closure__))
1576
1505
["func"].cell_contents)
1578
nonmethod_func = (dict(
1579
zip(attribute.__code__.co_freevars,
1580
attribute.__closure__))
1581
["func"].cell_contents)
1507
nonmethod_func = attribute
1582
1508
# Create a new, but exactly alike, function
1583
1509
# object, and decorate it to be a new D-Bus signal
1584
1510
# with the alternate D-Bus interface name
1585
new_function = copy_function(nonmethod_func)
1511
if sys.version_info.major == 2:
1512
new_function = types.FunctionType(
1513
nonmethod_func.func_code,
1514
nonmethod_func.func_globals,
1515
nonmethod_func.func_name,
1516
nonmethod_func.func_defaults,
1517
nonmethod_func.func_closure)
1519
new_function = types.FunctionType(
1520
nonmethod_func.__code__,
1521
nonmethod_func.__globals__,
1522
nonmethod_func.__name__,
1523
nonmethod_func.__defaults__,
1524
nonmethod_func.__closure__)
1586
1525
new_function = (dbus.service.signal(
1588
1527
attribute._dbus_signature)(new_function))
1628
1567
attribute._dbus_in_signature,
1629
1568
attribute._dbus_out_signature)
1630
(copy_function(attribute)))
1569
(types.FunctionType(attribute.func_code,
1570
attribute.func_globals,
1571
attribute.func_name,
1572
attribute.func_defaults,
1573
attribute.func_closure)))
1631
1574
# Copy annotations, if any
1633
1576
attr[attrname]._dbus_annotations = dict(
1645
1588
attribute._dbus_access,
1646
1589
attribute._dbus_get_args_options
1647
1590
["byte_arrays"])
1648
(copy_function(attribute)))
1591
(types.FunctionType(
1592
attribute.func_code,
1593
attribute.func_globals,
1594
attribute.func_name,
1595
attribute.func_defaults,
1596
attribute.func_closure)))
1649
1597
# Copy annotations, if any
1651
1599
attr[attrname]._dbus_annotations = dict(
1660
1608
# to the class.
1661
1609
attr[attrname] = (
1662
1610
dbus_interface_annotations(alt_interface)
1663
(copy_function(attribute)))
1611
(types.FunctionType(attribute.func_code,
1612
attribute.func_globals,
1613
attribute.func_name,
1614
attribute.func_defaults,
1615
attribute.func_closure)))
1665
1617
# Deprecate all alternate interfaces
1666
1618
iname="_AlternateDBusNames_interface_annotation{}"
1679
1631
if interface_names:
1680
1632
# Replace the class with a new subclass of it with
1681
1633
# 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__),
1634
cls = type(b"{}Alternate".format(cls.__name__),
1849
1797
def approve(self, value=True):
1850
1798
self.approved = value
1851
GLib.timeout_add(int(self.approval_duration.total_seconds()
1852
* 1000), self._reset_approved)
1799
gobject.timeout_add(int(self.approval_duration.total_seconds()
1800
* 1000), self._reset_approved)
1853
1801
self.send_changedstate()
1855
1803
## D-Bus methods, signals & properties
2065
2013
if (getattr(self, "disable_initiator_tag", None)
2068
GLib.source_remove(self.disable_initiator_tag)
2069
self.disable_initiator_tag = GLib.timeout_add(
2016
gobject.source_remove(self.disable_initiator_tag)
2017
self.disable_initiator_tag = gobject.timeout_add(
2070
2018
int((self.expires - now).total_seconds() * 1000),
2093
2041
if self.enabled:
2094
2042
# Reschedule checker run
2095
GLib.source_remove(self.checker_initiator_tag)
2096
self.checker_initiator_tag = GLib.timeout_add(
2043
gobject.source_remove(self.checker_initiator_tag)
2044
self.checker_initiator_tag = gobject.timeout_add(
2097
2045
value, self.start_checker)
2098
2046
self.start_checker() # Start one now, too
2191
2139
priority = self.server.gnutls_priority
2192
2140
if priority is None:
2193
2141
priority = "NORMAL"
2194
gnutls.priority_set_direct(session._c_object,
2195
priority.encode("utf-8"),
2142
gnutls.priority_set_direct(session._c_object, priority,
2198
2145
# Start communication using the Mandos protocol
2455
2402
bind to an address or port if they were not specified."""
2456
2403
if self.interface is not None:
2457
2404
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)
2405
logger.error("SO_BINDTODEVICE does not exist;"
2406
" cannot bind to interface %s",
2410
self.socket.setsockopt(
2411
socket.SOL_SOCKET, SO_BINDTODEVICE,
2412
(self.interface + "\0").encode("utf-8"))
2413
except socket.error as error:
2414
if error.errno == errno.EPERM:
2415
logger.error("No permission to bind to"
2416
" interface %s", self.interface)
2417
elif error.errno == errno.ENOPROTOOPT:
2418
logger.error("SO_BINDTODEVICE not available;"
2419
" cannot bind to interface %s",
2421
elif error.errno == errno.ENODEV:
2422
logger.error("Interface %s does not exist,"
2423
" cannot bind", self.interface)
2479
2426
# Only bind(2) the socket if we really need to.
2480
2427
if self.server_address[0] or self.server_address[1]:
2481
2428
if not self.server_address[0]:
2504
2451
gnutls_priority GnuTLS priority string
2505
2452
use_dbus: Boolean; to emit D-Bus signals or not
2507
Assumes a GLib.MainLoop event loop.
2454
Assumes a gobject.MainLoop event loop.
2510
2457
def __init__(self, server_address, RequestHandlerClass,
2536
2483
def add_pipe(self, parent_pipe, proc):
2537
2484
# Call "handle_ipc" for both data and EOF events
2485
gobject.io_add_watch(
2539
2486
parent_pipe.fileno(),
2540
GLib.IO_IN | GLib.IO_HUP,
2487
gobject.IO_IN | gobject.IO_HUP,
2541
2488
functools.partial(self.handle_ipc,
2542
2489
parent_pipe = parent_pipe,
2574
2521
parent_pipe.send(False)
2524
gobject.io_add_watch(
2578
2525
parent_pipe.fileno(),
2579
GLib.IO_IN | GLib.IO_HUP,
2526
gobject.IO_IN | gobject.IO_HUP,
2580
2527
functools.partial(self.handle_ipc,
2581
2528
parent_pipe = parent_pipe,
2964
2911
logger.error("Could not open file %r", pidfilename,
2967
for name, group in (("_mandos", "_mandos"),
2968
("mandos", "mandos"),
2969
("nobody", "nogroup")):
2914
for name in ("_mandos", "mandos", "nobody"):
2971
2916
uid = pwd.getpwnam(name).pw_uid
2972
gid = pwd.getpwnam(group).pw_gid
2917
gid = pwd.getpwnam(name).pw_gid
2974
2919
except KeyError:
2983
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2985
2927
except OSError as error:
2986
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2987
.format(uid, gid, os.strerror(error.errno)))
2988
2928
if error.errno != errno.EPERM:
3012
2952
# 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.
2955
# multiprocessing will use threads, so before we use gobject we
2956
# need to inform gobject that threads will be used.
2957
gobject.threads_init()
3019
2959
global main_loop
3020
2960
# From the Avahi example code
3021
2961
DBusGMainLoop(set_as_default=True)
3022
main_loop = GLib.MainLoop()
2962
main_loop = gobject.MainLoop()
3023
2963
bus = dbus.SystemBus()
3024
2964
# End of Avahi example code
3069
3009
if server_settings["restore"]:
3071
3011
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"]
3012
clients_data, old_client_settings = pickle.load(
3117
3014
os.remove(stored_state_path)
3118
3015
except IOError as e:
3119
3016
if e.errno == errno.ENOENT:
3221
3118
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)
3120
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3121
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3270
3166
return dbus.Dictionary(
3271
3167
{ c.dbus_object_path: c.GetAll(
3272
3168
"se.recompile.Mandos.Client")
3273
for c in tcp_server.clients.values() },
3169
for c in tcp_server.clients.itervalues() },
3274
3170
signature="oa{sv}")
3276
3172
@dbus.service.method(_interface, in_signature="o")
3277
3173
def RemoveClient(self, object_path):
3279
for c in tcp_server.clients.values():
3175
for c in tcp_server.clients.itervalues():
3280
3176
if c.dbus_object_path == object_path:
3281
3177
del tcp_server.clients[c.name]
3282
3178
c.remove_from_connection()
3328
3224
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
3227
"Cleanup function; run on exit"
3337
3229
service.cleanup()
3339
mp.active_children()
3231
multiprocessing.active_children()
3341
3233
if not (tcp_server.clients or client_settings):
3346
3238
# removed/edited, old secret will thus be unrecovable.
3348
3240
with PGPEngine() as pgp:
3349
for client in tcp_server.clients.values():
3241
for client in tcp_server.clients.itervalues():
3350
3242
key = client_settings[client.name]["secret"]
3351
3243
client.encrypted_secret = pgp.encrypt(client.secret,
3376
3268
prefix='clients-',
3377
3269
dir=os.path.dirname(stored_state_path),
3378
3270
delete=False) as stored_state:
3379
pickle.dump((clients, client_settings), stored_state,
3271
pickle.dump((clients, client_settings), stored_state)
3381
3272
tempname = stored_state.name
3382
3273
os.rename(tempname, stored_state_path)
3383
3274
except (IOError, OSError) as e:
3444
3335
# 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))
3337
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3338
lambda *args, **kwargs:
3339
(tcp_server.handle_request
3340
(*args[2:], **kwargs) or True))
3451
3342
logger.debug("Starting main loop")
3452
3343
main_loop.run()