11
11
# "AvahiService" class, and some lines in "main".
13
13
# Everything else is
14
# Copyright © 2008-2015 Teddy Hogeborn
15
# Copyright © 2008-2015 Björn Påhlsson
14
# Copyright © 2008-2016 Teddy Hogeborn
15
# Copyright © 2008-2016 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,
37
from future_builtins import *
38
from future_builtins import *
40
43
import SocketServer as socketserver
119
118
return interface_index
121
def copy_function(func):
122
"""Make a copy of a function"""
123
if sys.version_info.major == 2:
124
return types.FunctionType(func.func_code,
130
return types.FunctionType(func.__code__,
122
137
def initlogger(debug, level=logging.WARNING):
123
138
"""init logger and add loglevel"""
152
167
def __init__(self):
153
168
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
171
output = subprocess.check_output(["gpgconf"])
172
for line in output.splitlines():
173
name, text, path = line.split(b":")
178
if e.errno != errno.ENOENT:
154
180
self.gnupgargs = ['--batch',
155
'--home', self.tempdir,
181
'--homedir', self.tempdir,
158
184
'--no-use-agent']
197
223
dir=self.tempdir) as passfile:
198
224
passfile.write(passphrase)
200
proc = subprocess.Popen(['gpg', '--symmetric',
226
proc = subprocess.Popen([self.gpg, '--symmetric',
201
227
'--passphrase-file',
203
229
+ self.gnupgargs,
215
241
dir = self.tempdir) as passfile:
216
242
passfile.write(passphrase)
218
proc = subprocess.Popen(['gpg', '--decrypt',
244
proc = subprocess.Popen([self.gpg, '--decrypt',
219
245
'--passphrase-file',
221
247
+ self.gnupgargs,
227
253
raise PGPError(err)
228
254
return decrypted_plaintext
256
# Pretend that we have an Avahi module
258
"""This isn't so much a class as it is a module-like namespace.
259
It is instantiated once, and simulates having an Avahi module."""
260
IF_UNSPEC = -1 # avahi-common/address.h
261
PROTO_UNSPEC = -1 # avahi-common/address.h
262
PROTO_INET = 0 # avahi-common/address.h
263
PROTO_INET6 = 1 # avahi-common/address.h
264
DBUS_NAME = "org.freedesktop.Avahi"
265
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
266
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
267
DBUS_PATH_SERVER = "/"
268
def string_array_to_txt_array(self, t):
269
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
270
for s in t), signature="ay")
271
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
272
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
273
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
274
SERVER_INVALID = 0 # avahi-common/defs.h
275
SERVER_REGISTERING = 1 # avahi-common/defs.h
276
SERVER_RUNNING = 2 # avahi-common/defs.h
277
SERVER_COLLISION = 3 # avahi-common/defs.h
278
SERVER_FAILURE = 4 # avahi-common/defs.h
231
281
class AvahiError(Exception):
232
282
def __init__(self, value, *args, **kwargs):
437
487
_library = ctypes.cdll.LoadLibrary(
438
488
ctypes.util.find_library("gnutls"))
439
_need_version = "3.3.0"
489
_need_version = b"3.3.0"
440
490
def __init__(self):
441
491
# Need to use class name "GnuTLS" here, since this method is
442
492
# called before the assignment to the "gnutls" global variable
476
526
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
477
527
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
478
528
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
479
credentials_type_t = ctypes.c_int #
529
credentials_type_t = ctypes.c_int
480
530
transport_ptr_t = ctypes.c_void_p
481
531
close_request_t = ctypes.c_int
704
754
checker: subprocess.Popen(); a running checker process used
705
755
to see if the client lives.
706
756
'None' if no process is running.
707
checker_callback_tag: a gobject event source tag, or None
757
checker_callback_tag: a GLib event source tag, or None
708
758
checker_command: string; External command which is run to check
709
759
if client lives. %() expansions are done at
710
760
runtime with vars(self) as dict, so that for
711
761
instance %(name)s can be used in the command.
712
checker_initiator_tag: a gobject event source tag, or None
762
checker_initiator_tag: a GLib event source tag, or None
713
763
created: datetime.datetime(); (UTC) object creation
714
764
client_structure: Object describing what attributes a client has
715
765
and is used for storing the client at exit
716
766
current_checker_command: string; current running checker_command
717
disable_initiator_tag: a gobject event source tag, or None
767
disable_initiator_tag: a GLib event source tag, or None
719
769
fingerprint: string (40 or 32 hexadecimal digits); used to
720
770
uniquely identify the client
783
833
client["fingerprint"] = (section["fingerprint"].upper()
784
834
.replace(" ", ""))
785
835
if "secret" in section:
786
client["secret"] = section["secret"].decode("base64")
836
client["secret"] = codecs.decode(section["secret"]
787
839
elif "secfile" in section:
788
840
with open(os.path.expanduser(os.path.expandvars
789
841
(section["secfile"])),
842
894
self.changedstate = multiprocessing_manager.Condition(
843
895
multiprocessing_manager.Lock())
844
896
self.client_structure = [attr
845
for attr in self.__dict__.iterkeys()
897
for attr in self.__dict__.keys()
846
898
if not attr.startswith("_")]
847
899
self.client_structure.append("client_structure")
875
927
logger.info("Disabling client %s", self.name)
876
928
if getattr(self, "disable_initiator_tag", None) is not None:
877
gobject.source_remove(self.disable_initiator_tag)
929
GLib.source_remove(self.disable_initiator_tag)
878
930
self.disable_initiator_tag = None
879
931
self.expires = None
880
932
if getattr(self, "checker_initiator_tag", None) is not None:
881
gobject.source_remove(self.checker_initiator_tag)
933
GLib.source_remove(self.checker_initiator_tag)
882
934
self.checker_initiator_tag = None
883
935
self.stop_checker()
884
936
self.enabled = False
886
938
self.send_changedstate()
887
# Do not run this again if called by a gobject.timeout_add
939
# Do not run this again if called by a GLib.timeout_add
890
942
def __del__(self):
894
946
# Schedule a new checker to be started an 'interval' from now,
895
947
# and every interval from then on.
896
948
if self.checker_initiator_tag is not None:
897
gobject.source_remove(self.checker_initiator_tag)
898
self.checker_initiator_tag = gobject.timeout_add(
949
GLib.source_remove(self.checker_initiator_tag)
950
self.checker_initiator_tag = GLib.timeout_add(
899
951
int(self.interval.total_seconds() * 1000),
900
952
self.start_checker)
901
953
# Schedule a disable() when 'timeout' has passed
902
954
if self.disable_initiator_tag is not None:
903
gobject.source_remove(self.disable_initiator_tag)
904
self.disable_initiator_tag = gobject.timeout_add(
955
GLib.source_remove(self.disable_initiator_tag)
956
self.disable_initiator_tag = GLib.timeout_add(
905
957
int(self.timeout.total_seconds() * 1000), self.disable)
906
958
# Also start a new checker *right now*.
907
959
self.start_checker()
943
995
if timeout is None:
944
996
timeout = self.timeout
945
997
if self.disable_initiator_tag is not None:
946
gobject.source_remove(self.disable_initiator_tag)
998
GLib.source_remove(self.disable_initiator_tag)
947
999
self.disable_initiator_tag = None
948
1000
if getattr(self, "enabled", False):
949
self.disable_initiator_tag = gobject.timeout_add(
1001
self.disable_initiator_tag = GLib.timeout_add(
950
1002
int(timeout.total_seconds() * 1000), self.disable)
951
1003
self.expires = datetime.datetime.utcnow() + timeout
1007
1059
args = (pipe[1], subprocess.call, command),
1008
1060
kwargs = popen_args)
1009
1061
self.checker.start()
1010
self.checker_callback_tag = gobject.io_add_watch(
1011
pipe[0].fileno(), gobject.IO_IN,
1062
self.checker_callback_tag = GLib.io_add_watch(
1063
pipe[0].fileno(), GLib.IO_IN,
1012
1064
self.checker_callback, pipe[0], command)
1013
# Re-run this periodically if run by gobject.timeout_add
1065
# Re-run this periodically if run by GLib.timeout_add
1016
1068
def stop_checker(self):
1017
1069
"""Force the checker process, if any, to stop."""
1018
1070
if self.checker_callback_tag:
1019
gobject.source_remove(self.checker_callback_tag)
1071
GLib.source_remove(self.checker_callback_tag)
1020
1072
self.checker_callback_tag = None
1021
1073
if getattr(self, "checker", None) is None:
1496
1548
interface_names.add(alt_interface)
1497
1549
# Is this a D-Bus signal?
1498
1550
if getattr(attribute, "_dbus_is_signal", False):
1551
# Extract the original non-method undecorated
1552
# function by black magic
1499
1553
if sys.version_info.major == 2:
1500
# Extract the original non-method undecorated
1501
# function by black magic
1502
1554
nonmethod_func = (dict(
1503
1555
zip(attribute.func_code.co_freevars,
1504
1556
attribute.__closure__))
1505
1557
["func"].cell_contents)
1507
nonmethod_func = attribute
1559
nonmethod_func = (dict(
1560
zip(attribute.__code__.co_freevars,
1561
attribute.__closure__))
1562
["func"].cell_contents)
1508
1563
# Create a new, but exactly alike, function
1509
1564
# object, and decorate it to be a new D-Bus signal
1510
1565
# with the alternate D-Bus interface name
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__)
1566
new_function = copy_function(nonmethod_func)
1525
1567
new_function = (dbus.service.signal(
1527
1569
attribute._dbus_signature)(new_function))
1567
1609
attribute._dbus_in_signature,
1568
1610
attribute._dbus_out_signature)
1569
(types.FunctionType(attribute.func_code,
1570
attribute.func_globals,
1571
attribute.func_name,
1572
attribute.func_defaults,
1573
attribute.func_closure)))
1611
(copy_function(attribute)))
1574
1612
# Copy annotations, if any
1576
1614
attr[attrname]._dbus_annotations = dict(
1588
1626
attribute._dbus_access,
1589
1627
attribute._dbus_get_args_options
1590
1628
["byte_arrays"])
1591
(types.FunctionType(
1592
attribute.func_code,
1593
attribute.func_globals,
1594
attribute.func_name,
1595
attribute.func_defaults,
1596
attribute.func_closure)))
1629
(copy_function(attribute)))
1597
1630
# Copy annotations, if any
1599
1632
attr[attrname]._dbus_annotations = dict(
1608
1641
# to the class.
1609
1642
attr[attrname] = (
1610
1643
dbus_interface_annotations(alt_interface)
1611
(types.FunctionType(attribute.func_code,
1612
attribute.func_globals,
1613
attribute.func_name,
1614
attribute.func_defaults,
1615
attribute.func_closure)))
1644
(copy_function(attribute)))
1617
1646
# Deprecate all alternate interfaces
1618
1647
iname="_AlternateDBusNames_interface_annotation{}"
1631
1660
if interface_names:
1632
1661
# Replace the class with a new subclass of it with
1633
1662
# methods, signals, etc. as created above.
1634
cls = type(b"{}Alternate".format(cls.__name__),
1663
if sys.version_info.major == 2:
1664
cls = type(b"{}Alternate".format(cls.__name__),
1667
cls = type("{}Alternate".format(cls.__name__),
1797
1830
def approve(self, value=True):
1798
1831
self.approved = value
1799
gobject.timeout_add(int(self.approval_duration.total_seconds()
1800
* 1000), self._reset_approved)
1832
GLib.timeout_add(int(self.approval_duration.total_seconds()
1833
* 1000), self._reset_approved)
1801
1834
self.send_changedstate()
1803
1836
## D-Bus methods, signals & properties
2013
2046
if (getattr(self, "disable_initiator_tag", None)
2016
gobject.source_remove(self.disable_initiator_tag)
2017
self.disable_initiator_tag = gobject.timeout_add(
2049
GLib.source_remove(self.disable_initiator_tag)
2050
self.disable_initiator_tag = GLib.timeout_add(
2018
2051
int((self.expires - now).total_seconds() * 1000),
2041
2074
if self.enabled:
2042
2075
# Reschedule checker run
2043
gobject.source_remove(self.checker_initiator_tag)
2044
self.checker_initiator_tag = gobject.timeout_add(
2076
GLib.source_remove(self.checker_initiator_tag)
2077
self.checker_initiator_tag = GLib.timeout_add(
2045
2078
value, self.start_checker)
2046
2079
self.start_checker() # Start one now, too
2451
2484
gnutls_priority GnuTLS priority string
2452
2485
use_dbus: Boolean; to emit D-Bus signals or not
2454
Assumes a gobject.MainLoop event loop.
2487
Assumes a GLib.MainLoop event loop.
2457
2490
def __init__(self, server_address, RequestHandlerClass,
2483
2516
def add_pipe(self, parent_pipe, proc):
2484
2517
# Call "handle_ipc" for both data and EOF events
2485
gobject.io_add_watch(
2486
2519
parent_pipe.fileno(),
2487
gobject.IO_IN | gobject.IO_HUP,
2520
GLib.IO_IN | GLib.IO_HUP,
2488
2521
functools.partial(self.handle_ipc,
2489
2522
parent_pipe = parent_pipe,
2495
2528
client_object=None):
2496
2529
# error, or the other end of multiprocessing.Pipe has closed
2497
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2530
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2498
2531
# Wait for other process to exit
2521
2554
parent_pipe.send(False)
2524
gobject.io_add_watch(
2525
2558
parent_pipe.fileno(),
2526
gobject.IO_IN | gobject.IO_HUP,
2559
GLib.IO_IN | GLib.IO_HUP,
2527
2560
functools.partial(self.handle_ipc,
2528
2561
parent_pipe = parent_pipe,
2911
2944
logger.error("Could not open file %r", pidfilename,
2914
for name in ("_mandos", "mandos", "nobody"):
2947
for name, group in (("_mandos", "_mandos"),
2948
("mandos", "mandos"),
2949
("nobody", "nogroup")):
2916
2951
uid = pwd.getpwnam(name).pw_uid
2917
gid = pwd.getpwnam(name).pw_gid
2952
gid = pwd.getpwnam(group).pw_gid
2919
2954
except KeyError:
2963
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2927
2965
except OSError as error:
2966
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2967
.format(uid, gid, os.strerror(error.errno)))
2928
2968
if error.errno != errno.EPERM:
2952
2992
# Close all input and output, do double fork, etc.
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()
2995
# multiprocessing will use threads, so before we use GLib we need
2996
# to inform GLib that threads will be used.
2959
2999
global main_loop
2960
3000
# From the Avahi example code
2961
3001
DBusGMainLoop(set_as_default=True)
2962
main_loop = gobject.MainLoop()
3002
main_loop = GLib.MainLoop()
2963
3003
bus = dbus.SystemBus()
2964
3004
# End of Avahi example code
3009
3049
if server_settings["restore"]:
3011
3051
with open(stored_state_path, "rb") as stored_state:
3012
clients_data, old_client_settings = pickle.load(
3052
if sys.version_info.major == 2:
3053
clients_data, old_client_settings = pickle.load(
3056
bytes_clients_data, bytes_old_client_settings = (
3057
pickle.load(stored_state, encoding = "bytes"))
3058
### Fix bytes to strings
3061
clients_data = { (key.decode("utf-8")
3062
if isinstance(key, bytes)
3065
bytes_clients_data.items() }
3066
del bytes_clients_data
3067
for key in clients_data:
3068
value = { (k.decode("utf-8")
3069
if isinstance(k, bytes) else k): v
3071
clients_data[key].items() }
3072
clients_data[key] = value
3074
value["client_structure"] = [
3076
if isinstance(s, bytes)
3078
value["client_structure"] ]
3080
for k in ("name", "host"):
3081
if isinstance(value[k], bytes):
3082
value[k] = value[k].decode("utf-8")
3083
## old_client_settings
3085
old_client_settings = {
3086
(key.decode("utf-8")
3087
if isinstance(key, bytes)
3090
bytes_old_client_settings.items() }
3091
del bytes_old_client_settings
3093
for value in old_client_settings.values():
3094
if isinstance(value["host"], bytes):
3095
value["host"] = (value["host"]
3014
3097
os.remove(stored_state_path)
3015
3098
except IOError as e:
3016
3099
if e.errno == errno.ENOENT:
3118
3201
del pidfilename
3120
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3121
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3203
for termsig in (signal.SIGHUP, signal.SIGTERM):
3204
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3205
lambda: main_loop.quit() and False)
3166
3250
return dbus.Dictionary(
3167
3251
{ c.dbus_object_path: c.GetAll(
3168
3252
"se.recompile.Mandos.Client")
3169
for c in tcp_server.clients.itervalues() },
3253
for c in tcp_server.clients.values() },
3170
3254
signature="oa{sv}")
3172
3256
@dbus.service.method(_interface, in_signature="o")
3173
3257
def RemoveClient(self, object_path):
3175
for c in tcp_server.clients.itervalues():
3259
for c in tcp_server.clients.values():
3176
3260
if c.dbus_object_path == object_path:
3177
3261
del tcp_server.clients[c.name]
3178
3262
c.remove_from_connection()
3238
3322
# removed/edited, old secret will thus be unrecovable.
3240
3324
with PGPEngine() as pgp:
3241
for client in tcp_server.clients.itervalues():
3325
for client in tcp_server.clients.values():
3242
3326
key = client_settings[client.name]["secret"]
3243
3327
client.encrypted_secret = pgp.encrypt(client.secret,
3268
3352
prefix='clients-',
3269
3353
dir=os.path.dirname(stored_state_path),
3270
3354
delete=False) as stored_state:
3271
pickle.dump((clients, client_settings), stored_state)
3355
pickle.dump((clients, client_settings), stored_state,
3272
3357
tempname = stored_state.name
3273
3358
os.rename(tempname, stored_state_path)
3274
3359
except (IOError, OSError) as e:
3335
3420
# End of Avahi example code
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))
3422
GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
3423
lambda *args, **kwargs:
3424
(tcp_server.handle_request
3425
(*args[2:], **kwargs) or True))
3342
3427
logger.debug("Starting main loop")
3343
3428
main_loop.run()