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
118
119
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__,
137
122
def initlogger(debug, level=logging.WARNING):
138
123
"""init logger and add loglevel"""
167
152
def __init__(self):
168
153
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:
180
154
self.gnupgargs = ['--batch',
181
'--homedir', self.tempdir,
155
'--home', self.tempdir,
184
158
'--no-use-agent']
223
197
dir=self.tempdir) as passfile:
224
198
passfile.write(passphrase)
226
proc = subprocess.Popen([self.gpg, '--symmetric',
200
proc = subprocess.Popen(['gpg', '--symmetric',
227
201
'--passphrase-file',
229
203
+ self.gnupgargs,
241
215
dir = self.tempdir) as passfile:
242
216
passfile.write(passphrase)
244
proc = subprocess.Popen([self.gpg, '--decrypt',
218
proc = subprocess.Popen(['gpg', '--decrypt',
245
219
'--passphrase-file',
247
221
+ self.gnupgargs,
253
227
raise PGPError(err)
254
228
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
281
231
class AvahiError(Exception):
282
232
def __init__(self, value, *args, **kwargs):
487
437
_library = ctypes.cdll.LoadLibrary(
488
438
ctypes.util.find_library("gnutls"))
489
_need_version = b"3.3.0"
439
_need_version = "3.3.0"
490
440
def __init__(self):
491
441
# Need to use class name "GnuTLS" here, since this method is
492
442
# called before the assignment to the "gnutls" global variable
526
474
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
527
475
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
528
476
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
529
credentials_type_t = ctypes.c_int
477
credentials_type_t = ctypes.c_int #
530
478
transport_ptr_t = ctypes.c_void_p
531
479
close_request_t = ctypes.c_int
535
483
# We need to use the class name "GnuTLS" here, since this
536
484
# exception might be raised from within GnuTLS.__init__,
537
485
# which is called before the assignment to the "gnutls"
538
# global variable has happened.
486
# global variable happens.
539
487
def __init__(self, message = None, code = None, args=()):
540
488
# Default usage is by a message string, but if a return
541
489
# code is passed, convert it to a string with
542
490
# gnutls.strerror()
544
491
if message is None and code is not None:
545
492
message = GnuTLS.strerror(code)
546
493
return super(GnuTLS.Error, self).__init__(
585
532
def send(self, data):
586
533
data = bytes(data)
589
data_len -= gnutls.record_send(self._c_object,
536
return gnutls.record_send(self._c_object, data, len(data))
594
539
return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
596
# Error handling functions
541
# Error handling function
597
542
def _error_code(result):
598
543
"""A function to raise exceptions on errors, suitable
599
544
for the 'restype' attribute on ctypes functions"""
603
548
raise gnutls.CertificateSecurityError(code = result)
604
549
raise gnutls.Error(code = result)
606
def _retry_on_error(result, func, arguments):
607
"""A function to retry on some errors, suitable
608
for the 'errcheck' attribute on ctypes functions"""
610
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
611
return _error_code(result)
612
result = func(*arguments)
615
551
# Unless otherwise indicated, the function declarations below are
616
552
# all from the gnutls/gnutls.h C header file.
633
569
record_send.argtypes = [session_t, ctypes.c_void_p,
635
571
record_send.restype = ctypes.c_ssize_t
636
record_send.errcheck = _retry_on_error
638
573
certificate_allocate_credentials = (
639
574
_library.gnutls_certificate_allocate_credentials)
685
620
handshake = _library.gnutls_handshake
686
621
handshake.argtypes = [session_t]
687
622
handshake.restype = _error_code
688
handshake.errcheck = _retry_on_error
690
624
transport_set_ptr = _library.gnutls_transport_set_ptr
691
625
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
694
628
bye = _library.gnutls_bye
695
629
bye.argtypes = [session_t, close_request_t]
696
630
bye.restype = _error_code
697
bye.errcheck = _retry_on_error
699
632
check_version = _library.gnutls_check_version
700
633
check_version.argtypes = [ctypes.c_char_p]
729
662
ctypes.c_size_t)]
730
663
openpgp_crt_get_fingerprint.restype = _error_code
732
# Remove non-public functions
733
del _error_code, _retry_on_error
665
# Remove non-public function
734
667
# Create the global "gnutls" object, simulating a module
735
668
gnutls = GnuTLS()
754
687
checker: subprocess.Popen(); a running checker process used
755
688
to see if the client lives.
756
689
'None' if no process is running.
757
checker_callback_tag: a GLib event source tag, or None
690
checker_callback_tag: a gobject event source tag, or None
758
691
checker_command: string; External command which is run to check
759
692
if client lives. %() expansions are done at
760
693
runtime with vars(self) as dict, so that for
761
694
instance %(name)s can be used in the command.
762
checker_initiator_tag: a GLib event source tag, or None
695
checker_initiator_tag: a gobject event source tag, or None
763
696
created: datetime.datetime(); (UTC) object creation
764
697
client_structure: Object describing what attributes a client has
765
698
and is used for storing the client at exit
766
699
current_checker_command: string; current running checker_command
767
disable_initiator_tag: a GLib event source tag, or None
700
disable_initiator_tag: a gobject event source tag, or None
769
702
fingerprint: string (40 or 32 hexadecimal digits); used to
770
703
uniquely identify the client
833
766
client["fingerprint"] = (section["fingerprint"].upper()
834
767
.replace(" ", ""))
835
768
if "secret" in section:
836
client["secret"] = codecs.decode(section["secret"]
769
client["secret"] = section["secret"].decode("base64")
839
770
elif "secfile" in section:
840
771
with open(os.path.expanduser(os.path.expandvars
841
772
(section["secfile"])),
894
825
self.changedstate = multiprocessing_manager.Condition(
895
826
multiprocessing_manager.Lock())
896
827
self.client_structure = [attr
897
for attr in self.__dict__.keys()
828
for attr in self.__dict__.iterkeys()
898
829
if not attr.startswith("_")]
899
830
self.client_structure.append("client_structure")
927
858
logger.info("Disabling client %s", self.name)
928
859
if getattr(self, "disable_initiator_tag", None) is not None:
929
GLib.source_remove(self.disable_initiator_tag)
860
gobject.source_remove(self.disable_initiator_tag)
930
861
self.disable_initiator_tag = None
931
862
self.expires = None
932
863
if getattr(self, "checker_initiator_tag", None) is not None:
933
GLib.source_remove(self.checker_initiator_tag)
864
gobject.source_remove(self.checker_initiator_tag)
934
865
self.checker_initiator_tag = None
935
866
self.stop_checker()
936
867
self.enabled = False
938
869
self.send_changedstate()
939
# Do not run this again if called by a GLib.timeout_add
870
# Do not run this again if called by a gobject.timeout_add
942
873
def __del__(self):
946
877
# Schedule a new checker to be started an 'interval' from now,
947
878
# and every interval from then on.
948
879
if self.checker_initiator_tag is not None:
949
GLib.source_remove(self.checker_initiator_tag)
950
self.checker_initiator_tag = GLib.timeout_add(
880
gobject.source_remove(self.checker_initiator_tag)
881
self.checker_initiator_tag = gobject.timeout_add(
951
882
int(self.interval.total_seconds() * 1000),
952
883
self.start_checker)
953
884
# Schedule a disable() when 'timeout' has passed
954
885
if self.disable_initiator_tag is not None:
955
GLib.source_remove(self.disable_initiator_tag)
956
self.disable_initiator_tag = GLib.timeout_add(
886
gobject.source_remove(self.disable_initiator_tag)
887
self.disable_initiator_tag = gobject.timeout_add(
957
888
int(self.timeout.total_seconds() * 1000), self.disable)
958
889
# Also start a new checker *right now*.
959
890
self.start_checker()
995
926
if timeout is None:
996
927
timeout = self.timeout
997
928
if self.disable_initiator_tag is not None:
998
GLib.source_remove(self.disable_initiator_tag)
929
gobject.source_remove(self.disable_initiator_tag)
999
930
self.disable_initiator_tag = None
1000
931
if getattr(self, "enabled", False):
1001
self.disable_initiator_tag = GLib.timeout_add(
932
self.disable_initiator_tag = gobject.timeout_add(
1002
933
int(timeout.total_seconds() * 1000), self.disable)
1003
934
self.expires = datetime.datetime.utcnow() + timeout
1059
990
args = (pipe[1], subprocess.call, command),
1060
991
kwargs = popen_args)
1061
992
self.checker.start()
1062
self.checker_callback_tag = GLib.io_add_watch(
1063
pipe[0].fileno(), GLib.IO_IN,
993
self.checker_callback_tag = gobject.io_add_watch(
994
pipe[0].fileno(), gobject.IO_IN,
1064
995
self.checker_callback, pipe[0], command)
1065
# Re-run this periodically if run by GLib.timeout_add
996
# Re-run this periodically if run by gobject.timeout_add
1068
999
def stop_checker(self):
1069
1000
"""Force the checker process, if any, to stop."""
1070
1001
if self.checker_callback_tag:
1071
GLib.source_remove(self.checker_callback_tag)
1002
gobject.source_remove(self.checker_callback_tag)
1072
1003
self.checker_callback_tag = None
1073
1004
if getattr(self, "checker", None) is None:
1548
1479
interface_names.add(alt_interface)
1549
1480
# Is this a D-Bus signal?
1550
1481
if getattr(attribute, "_dbus_is_signal", False):
1551
# Extract the original non-method undecorated
1552
# function by black magic
1553
1482
if sys.version_info.major == 2:
1483
# Extract the original non-method undecorated
1484
# function by black magic
1554
1485
nonmethod_func = (dict(
1555
1486
zip(attribute.func_code.co_freevars,
1556
1487
attribute.__closure__))
1557
1488
["func"].cell_contents)
1559
nonmethod_func = (dict(
1560
zip(attribute.__code__.co_freevars,
1561
attribute.__closure__))
1562
["func"].cell_contents)
1490
nonmethod_func = attribute
1563
1491
# Create a new, but exactly alike, function
1564
1492
# object, and decorate it to be a new D-Bus signal
1565
1493
# with the alternate D-Bus interface name
1566
new_function = copy_function(nonmethod_func)
1494
if sys.version_info.major == 2:
1495
new_function = types.FunctionType(
1496
nonmethod_func.func_code,
1497
nonmethod_func.func_globals,
1498
nonmethod_func.func_name,
1499
nonmethod_func.func_defaults,
1500
nonmethod_func.func_closure)
1502
new_function = types.FunctionType(
1503
nonmethod_func.__code__,
1504
nonmethod_func.__globals__,
1505
nonmethod_func.__name__,
1506
nonmethod_func.__defaults__,
1507
nonmethod_func.__closure__)
1567
1508
new_function = (dbus.service.signal(
1569
1510
attribute._dbus_signature)(new_function))
1609
1550
attribute._dbus_in_signature,
1610
1551
attribute._dbus_out_signature)
1611
(copy_function(attribute)))
1552
(types.FunctionType(attribute.func_code,
1553
attribute.func_globals,
1554
attribute.func_name,
1555
attribute.func_defaults,
1556
attribute.func_closure)))
1612
1557
# Copy annotations, if any
1614
1559
attr[attrname]._dbus_annotations = dict(
1626
1571
attribute._dbus_access,
1627
1572
attribute._dbus_get_args_options
1628
1573
["byte_arrays"])
1629
(copy_function(attribute)))
1574
(types.FunctionType(
1575
attribute.func_code,
1576
attribute.func_globals,
1577
attribute.func_name,
1578
attribute.func_defaults,
1579
attribute.func_closure)))
1630
1580
# Copy annotations, if any
1632
1582
attr[attrname]._dbus_annotations = dict(
1641
1591
# to the class.
1642
1592
attr[attrname] = (
1643
1593
dbus_interface_annotations(alt_interface)
1644
(copy_function(attribute)))
1594
(types.FunctionType(attribute.func_code,
1595
attribute.func_globals,
1596
attribute.func_name,
1597
attribute.func_defaults,
1598
attribute.func_closure)))
1646
1600
# Deprecate all alternate interfaces
1647
1601
iname="_AlternateDBusNames_interface_annotation{}"
1660
1614
if interface_names:
1661
1615
# Replace the class with a new subclass of it with
1662
1616
# methods, signals, etc. as created above.
1663
if sys.version_info.major == 2:
1664
cls = type(b"{}Alternate".format(cls.__name__),
1667
cls = type("{}Alternate".format(cls.__name__),
1617
cls = type(b"{}Alternate".format(cls.__name__),
1830
1780
def approve(self, value=True):
1831
1781
self.approved = value
1832
GLib.timeout_add(int(self.approval_duration.total_seconds()
1833
* 1000), self._reset_approved)
1782
gobject.timeout_add(int(self.approval_duration.total_seconds()
1783
* 1000), self._reset_approved)
1834
1784
self.send_changedstate()
1836
1786
## D-Bus methods, signals & properties
2046
1996
if (getattr(self, "disable_initiator_tag", None)
2049
GLib.source_remove(self.disable_initiator_tag)
2050
self.disable_initiator_tag = GLib.timeout_add(
1999
gobject.source_remove(self.disable_initiator_tag)
2000
self.disable_initiator_tag = gobject.timeout_add(
2051
2001
int((self.expires - now).total_seconds() * 1000),
2074
2024
if self.enabled:
2075
2025
# Reschedule checker run
2076
GLib.source_remove(self.checker_initiator_tag)
2077
self.checker_initiator_tag = GLib.timeout_add(
2026
gobject.source_remove(self.checker_initiator_tag)
2027
self.checker_initiator_tag = gobject.timeout_add(
2078
2028
value, self.start_checker)
2079
2029
self.start_checker() # Start one now, too
2266
2216
delay -= time2 - time
2269
session.send(client.secret)
2270
except gnutls.Error as error:
2271
logger.warning("gnutls send failed",
2219
while sent_size < len(client.secret):
2221
sent = session.send(client.secret[sent_size:])
2222
except gnutls.Error as error:
2223
logger.warning("gnutls send failed",
2226
logger.debug("Sent: %d, remaining: %d", sent,
2227
len(client.secret) - (sent_size
2275
2231
logger.info("Sending secret to %s", client.name)
2276
2232
# bump the timeout using extended_timeout
2484
2440
gnutls_priority GnuTLS priority string
2485
2441
use_dbus: Boolean; to emit D-Bus signals or not
2487
Assumes a GLib.MainLoop event loop.
2443
Assumes a gobject.MainLoop event loop.
2490
2446
def __init__(self, server_address, RequestHandlerClass,
2516
2472
def add_pipe(self, parent_pipe, proc):
2517
2473
# Call "handle_ipc" for both data and EOF events
2474
gobject.io_add_watch(
2519
2475
parent_pipe.fileno(),
2520
GLib.IO_IN | GLib.IO_HUP,
2476
gobject.IO_IN | gobject.IO_HUP,
2521
2477
functools.partial(self.handle_ipc,
2522
2478
parent_pipe = parent_pipe,
2528
2484
client_object=None):
2529
2485
# error, or the other end of multiprocessing.Pipe has closed
2530
if condition & (GLib.IO_ERR | GLib.IO_HUP):
2486
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2531
2487
# Wait for other process to exit
2554
2510
parent_pipe.send(False)
2513
gobject.io_add_watch(
2558
2514
parent_pipe.fileno(),
2559
GLib.IO_IN | GLib.IO_HUP,
2515
gobject.IO_IN | gobject.IO_HUP,
2560
2516
functools.partial(self.handle_ipc,
2561
2517
parent_pipe = parent_pipe,
2944
2900
logger.error("Could not open file %r", pidfilename,
2947
for name, group in (("_mandos", "_mandos"),
2948
("mandos", "mandos"),
2949
("nobody", "nogroup")):
2903
for name in ("_mandos", "mandos", "nobody"):
2951
2905
uid = pwd.getpwnam(name).pw_uid
2952
gid = pwd.getpwnam(group).pw_gid
2906
gid = pwd.getpwnam(name).pw_gid
2954
2908
except KeyError:
2963
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2965
2916
except OSError as error:
2966
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2967
.format(uid, gid, os.strerror(error.errno)))
2968
2917
if error.errno != errno.EPERM:
2992
2941
# Close all input and output, do double fork, etc.
2995
# multiprocessing will use threads, so before we use GLib we need
2996
# to inform GLib that threads will be used.
2944
# multiprocessing will use threads, so before we use gobject we
2945
# need to inform gobject that threads will be used.
2946
gobject.threads_init()
2999
2948
global main_loop
3000
2949
# From the Avahi example code
3001
2950
DBusGMainLoop(set_as_default=True)
3002
main_loop = GLib.MainLoop()
2951
main_loop = gobject.MainLoop()
3003
2952
bus = dbus.SystemBus()
3004
2953
# End of Avahi example code
3049
2998
if server_settings["restore"]:
3051
3000
with open(stored_state_path, "rb") as stored_state:
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"]
3001
clients_data, old_client_settings = pickle.load(
3097
3003
os.remove(stored_state_path)
3098
3004
except IOError as e:
3099
3005
if e.errno == errno.ENOENT:
3201
3107
del pidfilename
3203
for termsig in (signal.SIGHUP, signal.SIGTERM):
3204
GLib.unix_signal_add(GLib.PRIORITY_HIGH, termsig,
3205
lambda: main_loop.quit() and False)
3109
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
3110
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
3250
3155
return dbus.Dictionary(
3251
3156
{ c.dbus_object_path: c.GetAll(
3252
3157
"se.recompile.Mandos.Client")
3253
for c in tcp_server.clients.values() },
3158
for c in tcp_server.clients.itervalues() },
3254
3159
signature="oa{sv}")
3256
3161
@dbus.service.method(_interface, in_signature="o")
3257
3162
def RemoveClient(self, object_path):
3259
for c in tcp_server.clients.values():
3164
for c in tcp_server.clients.itervalues():
3260
3165
if c.dbus_object_path == object_path:
3261
3166
del tcp_server.clients[c.name]
3262
3167
c.remove_from_connection()
3322
3227
# removed/edited, old secret will thus be unrecovable.
3324
3229
with PGPEngine() as pgp:
3325
for client in tcp_server.clients.values():
3230
for client in tcp_server.clients.itervalues():
3326
3231
key = client_settings[client.name]["secret"]
3327
3232
client.encrypted_secret = pgp.encrypt(client.secret,
3352
3257
prefix='clients-',
3353
3258
dir=os.path.dirname(stored_state_path),
3354
3259
delete=False) as stored_state:
3355
pickle.dump((clients, client_settings), stored_state,
3260
pickle.dump((clients, client_settings), stored_state)
3357
3261
tempname = stored_state.name
3358
3262
os.rename(tempname, stored_state_path)
3359
3263
except (IOError, OSError) as e:
3420
3324
# End of Avahi example code
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))
3326
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3327
lambda *args, **kwargs:
3328
(tcp_server.handle_request
3329
(*args[2:], **kwargs) or True))
3427
3331
logger.debug("Starting main loop")
3428
3332
main_loop.run()