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
121
return interface_index
124
def copy_function(func):
125
"""Make a copy of a function"""
126
if sys.version_info.major == 2:
127
return types.FunctionType(func.func_code,
133
return types.FunctionType(func.__code__,
122
140
def initlogger(debug, level=logging.WARNING):
123
141
"""init logger and add loglevel"""
152
170
def __init__(self):
153
171
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
174
output = subprocess.check_output(["gpgconf"])
175
for line in output.splitlines():
176
name, text, path = line.split(b":")
181
if e.errno != errno.ENOENT:
154
183
self.gnupgargs = ['--batch',
155
'--home', self.tempdir,
184
'--homedir', self.tempdir,
158
187
'--no-use-agent']
197
226
dir=self.tempdir) as passfile:
198
227
passfile.write(passphrase)
200
proc = subprocess.Popen(['gpg', '--symmetric',
229
proc = subprocess.Popen([self.gpg, '--symmetric',
201
230
'--passphrase-file',
203
232
+ self.gnupgargs,
215
244
dir = self.tempdir) as passfile:
216
245
passfile.write(passphrase)
218
proc = subprocess.Popen(['gpg', '--decrypt',
247
proc = subprocess.Popen([self.gpg, '--decrypt',
219
248
'--passphrase-file',
221
250
+ self.gnupgargs,
227
256
raise PGPError(err)
228
257
return decrypted_plaintext
259
# Pretend that we have an Avahi module
261
"""This isn't so much a class as it is a module-like namespace.
262
It is instantiated once, and simulates having an Avahi module."""
263
IF_UNSPEC = -1 # avahi-common/address.h
264
PROTO_UNSPEC = -1 # avahi-common/address.h
265
PROTO_INET = 0 # avahi-common/address.h
266
PROTO_INET6 = 1 # avahi-common/address.h
267
DBUS_NAME = "org.freedesktop.Avahi"
268
DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
269
DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
270
DBUS_PATH_SERVER = "/"
271
def string_array_to_txt_array(self, t):
272
return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
273
for s in t), signature="ay")
274
ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
275
ENTRY_GROUP_COLLISION = 3 # avahi-common/defs.h
276
ENTRY_GROUP_FAILURE = 4 # avahi-common/defs.h
277
SERVER_INVALID = 0 # avahi-common/defs.h
278
SERVER_REGISTERING = 1 # avahi-common/defs.h
279
SERVER_RUNNING = 2 # avahi-common/defs.h
280
SERVER_COLLISION = 3 # avahi-common/defs.h
281
SERVER_FAILURE = 4 # avahi-common/defs.h
231
284
class AvahiError(Exception):
232
285
def __init__(self, value, *args, **kwargs):
437
490
_library = ctypes.cdll.LoadLibrary(
438
491
ctypes.util.find_library("gnutls"))
439
_need_version = "3.3.0"
492
_need_version = b"3.3.0"
440
493
def __init__(self):
441
494
# Need to use class name "GnuTLS" here, since this method is
442
495
# called before the assignment to the "gnutls" global variable
474
529
openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
475
530
openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
476
531
log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
477
credentials_type_t = ctypes.c_int #
532
credentials_type_t = ctypes.c_int
478
533
transport_ptr_t = ctypes.c_void_p
479
534
close_request_t = ctypes.c_int
483
538
# We need to use the class name "GnuTLS" here, since this
484
539
# exception might be raised from within GnuTLS.__init__,
485
540
# which is called before the assignment to the "gnutls"
486
# global variable happens.
541
# global variable has happened.
487
542
def __init__(self, message = None, code = None, args=()):
488
543
# Default usage is by a message string, but if a return
489
544
# code is passed, convert it to a string with
490
545
# gnutls.strerror()
491
547
if message is None and code is not None:
492
548
message = GnuTLS.strerror(code)
493
549
return super(GnuTLS.Error, self).__init__(
532
588
def send(self, data):
533
589
data = bytes(data)
536
return gnutls.record_send(self._c_object, data, len(data))
592
data_len -= gnutls.record_send(self._c_object,
539
597
return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
541
# Error handling function
599
# Error handling functions
542
600
def _error_code(result):
543
601
"""A function to raise exceptions on errors, suitable
544
602
for the 'restype' attribute on ctypes functions"""
548
606
raise gnutls.CertificateSecurityError(code = result)
549
607
raise gnutls.Error(code = result)
609
def _retry_on_error(result, func, arguments):
610
"""A function to retry on some errors, suitable
611
for the 'errcheck' attribute on ctypes functions"""
613
if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
614
return _error_code(result)
615
result = func(*arguments)
551
618
# Unless otherwise indicated, the function declarations below are
552
619
# all from the gnutls/gnutls.h C header file.
569
636
record_send.argtypes = [session_t, ctypes.c_void_p,
571
638
record_send.restype = ctypes.c_ssize_t
639
record_send.errcheck = _retry_on_error
573
641
certificate_allocate_credentials = (
574
642
_library.gnutls_certificate_allocate_credentials)
620
688
handshake = _library.gnutls_handshake
621
689
handshake.argtypes = [session_t]
622
690
handshake.restype = _error_code
691
handshake.errcheck = _retry_on_error
624
693
transport_set_ptr = _library.gnutls_transport_set_ptr
625
694
transport_set_ptr.argtypes = [session_t, transport_ptr_t]
628
697
bye = _library.gnutls_bye
629
698
bye.argtypes = [session_t, close_request_t]
630
699
bye.restype = _error_code
700
bye.errcheck = _retry_on_error
632
702
check_version = _library.gnutls_check_version
633
703
check_version.argtypes = [ctypes.c_char_p]
662
732
ctypes.c_size_t)]
663
733
openpgp_crt_get_fingerprint.restype = _error_code
665
# Remove non-public function
735
# Remove non-public functions
736
del _error_code, _retry_on_error
667
737
# Create the global "gnutls" object, simulating a module
668
738
gnutls = GnuTLS()
687
757
checker: subprocess.Popen(); a running checker process used
688
758
to see if the client lives.
689
759
'None' if no process is running.
690
checker_callback_tag: a gobject event source tag, or None
760
checker_callback_tag: a GObject event source tag, or None
691
761
checker_command: string; External command which is run to check
692
762
if client lives. %() expansions are done at
693
763
runtime with vars(self) as dict, so that for
694
764
instance %(name)s can be used in the command.
695
checker_initiator_tag: a gobject event source tag, or None
765
checker_initiator_tag: a GObject event source tag, or None
696
766
created: datetime.datetime(); (UTC) object creation
697
767
client_structure: Object describing what attributes a client has
698
768
and is used for storing the client at exit
699
769
current_checker_command: string; current running checker_command
700
disable_initiator_tag: a gobject event source tag, or None
770
disable_initiator_tag: a GObject event source tag, or None
702
772
fingerprint: string (40 or 32 hexadecimal digits); used to
703
773
uniquely identify the client
766
836
client["fingerprint"] = (section["fingerprint"].upper()
767
837
.replace(" ", ""))
768
838
if "secret" in section:
769
client["secret"] = section["secret"].decode("base64")
839
client["secret"] = codecs.decode(section["secret"]
770
842
elif "secfile" in section:
771
843
with open(os.path.expanduser(os.path.expandvars
772
844
(section["secfile"])),
825
897
self.changedstate = multiprocessing_manager.Condition(
826
898
multiprocessing_manager.Lock())
827
899
self.client_structure = [attr
828
for attr in self.__dict__.iterkeys()
900
for attr in self.__dict__.keys()
829
901
if not attr.startswith("_")]
830
902
self.client_structure.append("client_structure")
858
930
logger.info("Disabling client %s", self.name)
859
931
if getattr(self, "disable_initiator_tag", None) is not None:
860
gobject.source_remove(self.disable_initiator_tag)
932
GObject.source_remove(self.disable_initiator_tag)
861
933
self.disable_initiator_tag = None
862
934
self.expires = None
863
935
if getattr(self, "checker_initiator_tag", None) is not None:
864
gobject.source_remove(self.checker_initiator_tag)
936
GObject.source_remove(self.checker_initiator_tag)
865
937
self.checker_initiator_tag = None
866
938
self.stop_checker()
867
939
self.enabled = False
869
941
self.send_changedstate()
870
# Do not run this again if called by a gobject.timeout_add
942
# Do not run this again if called by a GObject.timeout_add
873
945
def __del__(self):
877
949
# Schedule a new checker to be started an 'interval' from now,
878
950
# and every interval from then on.
879
951
if self.checker_initiator_tag is not None:
880
gobject.source_remove(self.checker_initiator_tag)
881
self.checker_initiator_tag = gobject.timeout_add(
952
GObject.source_remove(self.checker_initiator_tag)
953
self.checker_initiator_tag = GObject.timeout_add(
882
954
int(self.interval.total_seconds() * 1000),
883
955
self.start_checker)
884
956
# Schedule a disable() when 'timeout' has passed
885
957
if self.disable_initiator_tag is not None:
886
gobject.source_remove(self.disable_initiator_tag)
887
self.disable_initiator_tag = gobject.timeout_add(
958
GObject.source_remove(self.disable_initiator_tag)
959
self.disable_initiator_tag = GObject.timeout_add(
888
960
int(self.timeout.total_seconds() * 1000), self.disable)
889
961
# Also start a new checker *right now*.
890
962
self.start_checker()
926
998
if timeout is None:
927
999
timeout = self.timeout
928
1000
if self.disable_initiator_tag is not None:
929
gobject.source_remove(self.disable_initiator_tag)
1001
GObject.source_remove(self.disable_initiator_tag)
930
1002
self.disable_initiator_tag = None
931
1003
if getattr(self, "enabled", False):
932
self.disable_initiator_tag = gobject.timeout_add(
1004
self.disable_initiator_tag = GObject.timeout_add(
933
1005
int(timeout.total_seconds() * 1000), self.disable)
934
1006
self.expires = datetime.datetime.utcnow() + timeout
990
1062
args = (pipe[1], subprocess.call, command),
991
1063
kwargs = popen_args)
992
1064
self.checker.start()
993
self.checker_callback_tag = gobject.io_add_watch(
994
pipe[0].fileno(), gobject.IO_IN,
1065
self.checker_callback_tag = GObject.io_add_watch(
1066
pipe[0].fileno(), GObject.IO_IN,
995
1067
self.checker_callback, pipe[0], command)
996
# Re-run this periodically if run by gobject.timeout_add
1068
# Re-run this periodically if run by GObject.timeout_add
999
1071
def stop_checker(self):
1000
1072
"""Force the checker process, if any, to stop."""
1001
1073
if self.checker_callback_tag:
1002
gobject.source_remove(self.checker_callback_tag)
1074
GObject.source_remove(self.checker_callback_tag)
1003
1075
self.checker_callback_tag = None
1004
1076
if getattr(self, "checker", None) is None:
1479
1551
interface_names.add(alt_interface)
1480
1552
# Is this a D-Bus signal?
1481
1553
if getattr(attribute, "_dbus_is_signal", False):
1554
# Extract the original non-method undecorated
1555
# function by black magic
1482
1556
if sys.version_info.major == 2:
1483
# Extract the original non-method undecorated
1484
# function by black magic
1485
1557
nonmethod_func = (dict(
1486
1558
zip(attribute.func_code.co_freevars,
1487
1559
attribute.__closure__))
1488
1560
["func"].cell_contents)
1490
nonmethod_func = attribute
1562
nonmethod_func = (dict(
1563
zip(attribute.__code__.co_freevars,
1564
attribute.__closure__))
1565
["func"].cell_contents)
1491
1566
# Create a new, but exactly alike, function
1492
1567
# object, and decorate it to be a new D-Bus signal
1493
1568
# with the alternate D-Bus interface name
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__)
1569
new_function = copy_function(nonmethod_func)
1508
1570
new_function = (dbus.service.signal(
1510
1572
attribute._dbus_signature)(new_function))
1550
1612
attribute._dbus_in_signature,
1551
1613
attribute._dbus_out_signature)
1552
(types.FunctionType(attribute.func_code,
1553
attribute.func_globals,
1554
attribute.func_name,
1555
attribute.func_defaults,
1556
attribute.func_closure)))
1614
(copy_function(attribute)))
1557
1615
# Copy annotations, if any
1559
1617
attr[attrname]._dbus_annotations = dict(
1571
1629
attribute._dbus_access,
1572
1630
attribute._dbus_get_args_options
1573
1631
["byte_arrays"])
1574
(types.FunctionType(
1575
attribute.func_code,
1576
attribute.func_globals,
1577
attribute.func_name,
1578
attribute.func_defaults,
1579
attribute.func_closure)))
1632
(copy_function(attribute)))
1580
1633
# Copy annotations, if any
1582
1635
attr[attrname]._dbus_annotations = dict(
1591
1644
# to the class.
1592
1645
attr[attrname] = (
1593
1646
dbus_interface_annotations(alt_interface)
1594
(types.FunctionType(attribute.func_code,
1595
attribute.func_globals,
1596
attribute.func_name,
1597
attribute.func_defaults,
1598
attribute.func_closure)))
1647
(copy_function(attribute)))
1600
1649
# Deprecate all alternate interfaces
1601
1650
iname="_AlternateDBusNames_interface_annotation{}"
1614
1663
if interface_names:
1615
1664
# Replace the class with a new subclass of it with
1616
1665
# methods, signals, etc. as created above.
1617
cls = type(b"{}Alternate".format(cls.__name__),
1666
if sys.version_info.major == 2:
1667
cls = type(b"{}Alternate".format(cls.__name__),
1670
cls = type("{}Alternate".format(cls.__name__),
1780
1833
def approve(self, value=True):
1781
1834
self.approved = value
1782
gobject.timeout_add(int(self.approval_duration.total_seconds()
1835
GObject.timeout_add(int(self.approval_duration.total_seconds()
1783
1836
* 1000), self._reset_approved)
1784
1837
self.send_changedstate()
1996
2049
if (getattr(self, "disable_initiator_tag", None)
1999
gobject.source_remove(self.disable_initiator_tag)
2000
self.disable_initiator_tag = gobject.timeout_add(
2052
GObject.source_remove(self.disable_initiator_tag)
2053
self.disable_initiator_tag = GObject.timeout_add(
2001
2054
int((self.expires - now).total_seconds() * 1000),
2024
2077
if self.enabled:
2025
2078
# Reschedule checker run
2026
gobject.source_remove(self.checker_initiator_tag)
2027
self.checker_initiator_tag = gobject.timeout_add(
2079
GObject.source_remove(self.checker_initiator_tag)
2080
self.checker_initiator_tag = GObject.timeout_add(
2028
2081
value, self.start_checker)
2029
2082
self.start_checker() # Start one now, too
2216
2269
delay -= time2 - time
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
2272
session.send(client.secret)
2273
except gnutls.Error as error:
2274
logger.warning("gnutls send failed",
2231
2278
logger.info("Sending secret to %s", client.name)
2232
2279
# bump the timeout using extended_timeout
2440
2487
gnutls_priority GnuTLS priority string
2441
2488
use_dbus: Boolean; to emit D-Bus signals or not
2443
Assumes a gobject.MainLoop event loop.
2490
Assumes a GObject.MainLoop event loop.
2446
2493
def __init__(self, server_address, RequestHandlerClass,
2472
2519
def add_pipe(self, parent_pipe, proc):
2473
2520
# Call "handle_ipc" for both data and EOF events
2474
gobject.io_add_watch(
2521
GObject.io_add_watch(
2475
2522
parent_pipe.fileno(),
2476
gobject.IO_IN | gobject.IO_HUP,
2523
GObject.IO_IN | GObject.IO_HUP,
2477
2524
functools.partial(self.handle_ipc,
2478
2525
parent_pipe = parent_pipe,
2484
2531
client_object=None):
2485
2532
# error, or the other end of multiprocessing.Pipe has closed
2486
if condition & (gobject.IO_ERR | gobject.IO_HUP):
2533
if condition & (GObject.IO_ERR | GObject.IO_HUP):
2487
2534
# Wait for other process to exit
2510
2557
parent_pipe.send(False)
2513
gobject.io_add_watch(
2560
GObject.io_add_watch(
2514
2561
parent_pipe.fileno(),
2515
gobject.IO_IN | gobject.IO_HUP,
2562
GObject.IO_IN | GObject.IO_HUP,
2516
2563
functools.partial(self.handle_ipc,
2517
2564
parent_pipe = parent_pipe,
2900
2947
logger.error("Could not open file %r", pidfilename,
2903
for name in ("_mandos", "mandos", "nobody"):
2950
for name, group in (("_mandos", "_mandos"),
2951
("mandos", "mandos"),
2952
("nobody", "nogroup")):
2905
2954
uid = pwd.getpwnam(name).pw_uid
2906
gid = pwd.getpwnam(name).pw_gid
2955
gid = pwd.getpwnam(group).pw_gid
2908
2957
except KeyError:
2966
logger.debug("Did setuid/setgid to {}:{}".format(uid,
2916
2968
except OSError as error:
2969
logger.warning("Failed to setuid/setgid to {}:{}: {}"
2970
.format(uid, gid, os.strerror(error.errno)))
2917
2971
if error.errno != errno.EPERM:
2941
2995
# Close all input and output, do double fork, etc.
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()
2998
# multiprocessing will use threads, so before we use GObject we
2999
# need to inform GObject that threads will be used.
3000
GObject.threads_init()
2948
3002
global main_loop
2949
3003
# From the Avahi example code
2950
3004
DBusGMainLoop(set_as_default=True)
2951
main_loop = gobject.MainLoop()
3005
main_loop = GObject.MainLoop()
2952
3006
bus = dbus.SystemBus()
2953
3007
# End of Avahi example code
2998
3052
if server_settings["restore"]:
3000
3054
with open(stored_state_path, "rb") as stored_state:
3001
clients_data, old_client_settings = pickle.load(
3055
if sys.version_info.major == 2:
3056
clients_data, old_client_settings = pickle.load(
3059
bytes_clients_data, bytes_old_client_settings = (
3060
pickle.load(stored_state, encoding = "bytes"))
3061
### Fix bytes to strings
3064
clients_data = { (key.decode("utf-8")
3065
if isinstance(key, bytes)
3068
bytes_clients_data.items() }
3069
del bytes_clients_data
3070
for key in clients_data:
3071
value = { (k.decode("utf-8")
3072
if isinstance(k, bytes) else k): v
3074
clients_data[key].items() }
3075
clients_data[key] = value
3077
value["client_structure"] = [
3079
if isinstance(s, bytes)
3081
value["client_structure"] ]
3083
for k in ("name", "host"):
3084
if isinstance(value[k], bytes):
3085
value[k] = value[k].decode("utf-8")
3086
## old_client_settings
3088
old_client_settings = {
3089
(key.decode("utf-8")
3090
if isinstance(key, bytes)
3093
bytes_old_client_settings.items() }
3094
del bytes_old_client_settings
3096
for value in old_client_settings.values():
3097
if isinstance(value["host"], bytes):
3098
value["host"] = (value["host"]
3003
3100
os.remove(stored_state_path)
3004
3101
except IOError as e:
3005
3102
if e.errno == errno.ENOENT:
3155
3252
return dbus.Dictionary(
3156
3253
{ c.dbus_object_path: c.GetAll(
3157
3254
"se.recompile.Mandos.Client")
3158
for c in tcp_server.clients.itervalues() },
3255
for c in tcp_server.clients.values() },
3159
3256
signature="oa{sv}")
3161
3258
@dbus.service.method(_interface, in_signature="o")
3162
3259
def RemoveClient(self, object_path):
3164
for c in tcp_server.clients.itervalues():
3261
for c in tcp_server.clients.values():
3165
3262
if c.dbus_object_path == object_path:
3166
3263
del tcp_server.clients[c.name]
3167
3264
c.remove_from_connection()
3227
3324
# removed/edited, old secret will thus be unrecovable.
3229
3326
with PGPEngine() as pgp:
3230
for client in tcp_server.clients.itervalues():
3327
for client in tcp_server.clients.values():
3231
3328
key = client_settings[client.name]["secret"]
3232
3329
client.encrypted_secret = pgp.encrypt(client.secret,
3257
3354
prefix='clients-',
3258
3355
dir=os.path.dirname(stored_state_path),
3259
3356
delete=False) as stored_state:
3260
pickle.dump((clients, client_settings), stored_state)
3357
pickle.dump((clients, client_settings), stored_state,
3261
3359
tempname = stored_state.name
3262
3360
os.rename(tempname, stored_state_path)
3263
3361
except (IOError, OSError) as e:
3324
3422
# End of Avahi example code
3326
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3424
GObject.io_add_watch(tcp_server.fileno(), GObject.IO_IN,
3327
3425
lambda *args, **kwargs:
3328
3426
(tcp_server.handle_request
3329
3427
(*args[2:], **kwargs) or True))