/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2016-03-07 23:39:36 UTC
  • Revision ID: teddy@recompile.se-20160307233936-mhgpxhggamde443n
Server bug fix: Include CAP_SETGID so it does not run as root

* debian/mandos.postinst (configure): If old version was 1.7.4-1 or
  1.7.4-1~bpo8+1, fix situation where clients.pickle file is owned by
  root.
* mandos (main): Print debug info about setuid() and setgid()
* mandos.service ([Service]/CapabilityBoundingSet): Add "CAP_KILL
  CAP_SETGID"; the latter is needed for setgid() to be allowed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python2.7
 
1
#!/usr/bin/python
2
2
# -*- mode: python; coding: utf-8 -*-
3
3
4
4
# Mandos server - give out binary blobs to connecting clients.
11
11
# "AvahiService" class, and some lines in "main".
12
12
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
16
16
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
77
77
import dbus
78
78
import dbus.service
79
79
try:
80
 
    import gobject
 
80
    from gi.repository import GObject
81
81
except ImportError:
82
 
    from gi.repository import GObject as gobject
 
82
    import gobject as GObject
83
83
import avahi
84
84
from dbus.mainloop.glib import DBusGMainLoop
85
85
import ctypes
98
98
if sys.version_info.major == 2:
99
99
    str = unicode
100
100
 
101
 
version = "1.7.1"
 
101
version = "1.7.4"
102
102
stored_state_file = "clients.pickle"
103
103
 
104
104
logger = logging.getLogger()
151
151
    
152
152
    def __init__(self):
153
153
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
 
154
        self.gpg = "gpg"
 
155
        try:
 
156
            output = subprocess.check_output(["gpgconf"])
 
157
            for line in output.splitlines():
 
158
                name, text, path = line.split(":")
 
159
                if name == "gpg":
 
160
                    self.gpg = path
 
161
                    break
 
162
        except OSError as e:
 
163
            if e.errno != errno.ENOENT:
 
164
                raise
154
165
        self.gnupgargs = ['--batch',
155
 
                          '--home', self.tempdir,
 
166
                          '--homedir', self.tempdir,
156
167
                          '--force-mdc',
157
168
                          '--quiet',
158
169
                          '--no-use-agent']
197
208
                dir=self.tempdir) as passfile:
198
209
            passfile.write(passphrase)
199
210
            passfile.flush()
200
 
            proc = subprocess.Popen(['gpg', '--symmetric',
 
211
            proc = subprocess.Popen([self.gpg, '--symmetric',
201
212
                                     '--passphrase-file',
202
213
                                     passfile.name]
203
214
                                    + self.gnupgargs,
215
226
                dir = self.tempdir) as passfile:
216
227
            passfile.write(passphrase)
217
228
            passfile.flush()
218
 
            proc = subprocess.Popen(['gpg', '--decrypt',
 
229
            proc = subprocess.Popen([self.gpg, '--decrypt',
219
230
                                     '--passphrase-file',
220
231
                                     passfile.name]
221
232
                                    + self.gnupgargs,
476
487
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
477
488
    openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
478
489
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
479
 
    credentials_type_t = ctypes.c_int # 
 
490
    credentials_type_t = ctypes.c_int
480
491
    transport_ptr_t = ctypes.c_void_p
481
492
    close_request_t = ctypes.c_int
482
493
    
704
715
    checker:    subprocess.Popen(); a running checker process used
705
716
                                    to see if the client lives.
706
717
                                    'None' if no process is running.
707
 
    checker_callback_tag: a gobject event source tag, or None
 
718
    checker_callback_tag: a GObject event source tag, or None
708
719
    checker_command: string; External command which is run to check
709
720
                     if client lives.  %() expansions are done at
710
721
                     runtime with vars(self) as dict, so that for
711
722
                     instance %(name)s can be used in the command.
712
 
    checker_initiator_tag: a gobject event source tag, or None
 
723
    checker_initiator_tag: a GObject event source tag, or None
713
724
    created:    datetime.datetime(); (UTC) object creation
714
725
    client_structure: Object describing what attributes a client has
715
726
                      and is used for storing the client at exit
716
727
    current_checker_command: string; current running checker_command
717
 
    disable_initiator_tag: a gobject event source tag, or None
 
728
    disable_initiator_tag: a GObject event source tag, or None
718
729
    enabled:    bool()
719
730
    fingerprint: string (40 or 32 hexadecimal digits); used to
720
731
                 uniquely identify the client
874
885
        if not quiet:
875
886
            logger.info("Disabling client %s", self.name)
876
887
        if getattr(self, "disable_initiator_tag", None) is not None:
877
 
            gobject.source_remove(self.disable_initiator_tag)
 
888
            GObject.source_remove(self.disable_initiator_tag)
878
889
            self.disable_initiator_tag = None
879
890
        self.expires = None
880
891
        if getattr(self, "checker_initiator_tag", None) is not None:
881
 
            gobject.source_remove(self.checker_initiator_tag)
 
892
            GObject.source_remove(self.checker_initiator_tag)
882
893
            self.checker_initiator_tag = None
883
894
        self.stop_checker()
884
895
        self.enabled = False
885
896
        if not quiet:
886
897
            self.send_changedstate()
887
 
        # Do not run this again if called by a gobject.timeout_add
 
898
        # Do not run this again if called by a GObject.timeout_add
888
899
        return False
889
900
    
890
901
    def __del__(self):
894
905
        # Schedule a new checker to be started an 'interval' from now,
895
906
        # and every interval from then on.
896
907
        if self.checker_initiator_tag is not None:
897
 
            gobject.source_remove(self.checker_initiator_tag)
898
 
        self.checker_initiator_tag = gobject.timeout_add(
 
908
            GObject.source_remove(self.checker_initiator_tag)
 
909
        self.checker_initiator_tag = GObject.timeout_add(
899
910
            int(self.interval.total_seconds() * 1000),
900
911
            self.start_checker)
901
912
        # Schedule a disable() when 'timeout' has passed
902
913
        if self.disable_initiator_tag is not None:
903
 
            gobject.source_remove(self.disable_initiator_tag)
904
 
        self.disable_initiator_tag = gobject.timeout_add(
 
914
            GObject.source_remove(self.disable_initiator_tag)
 
915
        self.disable_initiator_tag = GObject.timeout_add(
905
916
            int(self.timeout.total_seconds() * 1000), self.disable)
906
917
        # Also start a new checker *right now*.
907
918
        self.start_checker()
943
954
        if timeout is None:
944
955
            timeout = self.timeout
945
956
        if self.disable_initiator_tag is not None:
946
 
            gobject.source_remove(self.disable_initiator_tag)
 
957
            GObject.source_remove(self.disable_initiator_tag)
947
958
            self.disable_initiator_tag = None
948
959
        if getattr(self, "enabled", False):
949
 
            self.disable_initiator_tag = gobject.timeout_add(
 
960
            self.disable_initiator_tag = GObject.timeout_add(
950
961
                int(timeout.total_seconds() * 1000), self.disable)
951
962
            self.expires = datetime.datetime.utcnow() + timeout
952
963
    
1007
1018
                args = (pipe[1], subprocess.call, command),
1008
1019
                kwargs = popen_args)
1009
1020
            self.checker.start()
1010
 
            self.checker_callback_tag = gobject.io_add_watch(
1011
 
                pipe[0].fileno(), gobject.IO_IN,
 
1021
            self.checker_callback_tag = GObject.io_add_watch(
 
1022
                pipe[0].fileno(), GObject.IO_IN,
1012
1023
                self.checker_callback, pipe[0], command)
1013
 
        # Re-run this periodically if run by gobject.timeout_add
 
1024
        # Re-run this periodically if run by GObject.timeout_add
1014
1025
        return True
1015
1026
    
1016
1027
    def stop_checker(self):
1017
1028
        """Force the checker process, if any, to stop."""
1018
1029
        if self.checker_callback_tag:
1019
 
            gobject.source_remove(self.checker_callback_tag)
 
1030
            GObject.source_remove(self.checker_callback_tag)
1020
1031
            self.checker_callback_tag = None
1021
1032
        if getattr(self, "checker", None) is None:
1022
1033
            return
1796
1807
    
1797
1808
    def approve(self, value=True):
1798
1809
        self.approved = value
1799
 
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1810
        GObject.timeout_add(int(self.approval_duration.total_seconds()
1800
1811
                                * 1000), self._reset_approved)
1801
1812
        self.send_changedstate()
1802
1813
    
2013
2024
                if (getattr(self, "disable_initiator_tag", None)
2014
2025
                    is None):
2015
2026
                    return
2016
 
                gobject.source_remove(self.disable_initiator_tag)
2017
 
                self.disable_initiator_tag = gobject.timeout_add(
 
2027
                GObject.source_remove(self.disable_initiator_tag)
 
2028
                self.disable_initiator_tag = GObject.timeout_add(
2018
2029
                    int((self.expires - now).total_seconds() * 1000),
2019
2030
                    self.disable)
2020
2031
    
2040
2051
            return
2041
2052
        if self.enabled:
2042
2053
            # Reschedule checker run
2043
 
            gobject.source_remove(self.checker_initiator_tag)
2044
 
            self.checker_initiator_tag = gobject.timeout_add(
 
2054
            GObject.source_remove(self.checker_initiator_tag)
 
2055
            self.checker_initiator_tag = GObject.timeout_add(
2045
2056
                value, self.start_checker)
2046
2057
            self.start_checker() # Start one now, too
2047
2058
    
2451
2462
        gnutls_priority GnuTLS priority string
2452
2463
        use_dbus:       Boolean; to emit D-Bus signals or not
2453
2464
    
2454
 
    Assumes a gobject.MainLoop event loop.
 
2465
    Assumes a GObject.MainLoop event loop.
2455
2466
    """
2456
2467
    
2457
2468
    def __init__(self, server_address, RequestHandlerClass,
2482
2493
    
2483
2494
    def add_pipe(self, parent_pipe, proc):
2484
2495
        # Call "handle_ipc" for both data and EOF events
2485
 
        gobject.io_add_watch(
 
2496
        GObject.io_add_watch(
2486
2497
            parent_pipe.fileno(),
2487
 
            gobject.IO_IN | gobject.IO_HUP,
 
2498
            GObject.IO_IN | GObject.IO_HUP,
2488
2499
            functools.partial(self.handle_ipc,
2489
2500
                              parent_pipe = parent_pipe,
2490
2501
                              proc = proc))
2494
2505
                   proc = None,
2495
2506
                   client_object=None):
2496
2507
        # error, or the other end of multiprocessing.Pipe has closed
2497
 
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
 
2508
        if condition & (GObject.IO_ERR | GObject.IO_HUP):
2498
2509
            # Wait for other process to exit
2499
2510
            proc.join()
2500
2511
            return False
2521
2532
                parent_pipe.send(False)
2522
2533
                return False
2523
2534
            
2524
 
            gobject.io_add_watch(
 
2535
            GObject.io_add_watch(
2525
2536
                parent_pipe.fileno(),
2526
 
                gobject.IO_IN | gobject.IO_HUP,
 
2537
                GObject.IO_IN | GObject.IO_HUP,
2527
2538
                functools.partial(self.handle_ipc,
2528
2539
                                  parent_pipe = parent_pipe,
2529
2540
                                  proc = proc,
2911
2922
            logger.error("Could not open file %r", pidfilename,
2912
2923
                         exc_info=e)
2913
2924
    
2914
 
    for name in ("_mandos", "mandos", "nobody"):
 
2925
    for name, group in (("_mandos", "_mandos"),
 
2926
                        ("mandos", "mandos"),
 
2927
                        ("nobody", "nogroup")):
2915
2928
        try:
2916
2929
            uid = pwd.getpwnam(name).pw_uid
2917
 
            gid = pwd.getpwnam(name).pw_gid
 
2930
            gid = pwd.getpwnam(group).pw_gid
2918
2931
            break
2919
2932
        except KeyError:
2920
2933
            continue
2924
2937
    try:
2925
2938
        os.setgid(gid)
2926
2939
        os.setuid(uid)
 
2940
        if debug:
 
2941
            logger.debug("Did setuid/setgid to {}:{}".format(uid,
 
2942
                                                             gid))
2927
2943
    except OSError as error:
 
2944
        logger.warning("Failed to setuid/setgid to {}:{}: {}"
 
2945
                       .format(uid, gid, os.strerror(error.errno)))
2928
2946
        if error.errno != errno.EPERM:
2929
2947
            raise
2930
2948
    
2952
2970
        # Close all input and output, do double fork, etc.
2953
2971
        daemon()
2954
2972
    
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()
 
2973
    # multiprocessing will use threads, so before we use GObject we
 
2974
    # need to inform GObject that threads will be used.
 
2975
    GObject.threads_init()
2958
2976
    
2959
2977
    global main_loop
2960
2978
    # From the Avahi example code
2961
2979
    DBusGMainLoop(set_as_default=True)
2962
 
    main_loop = gobject.MainLoop()
 
2980
    main_loop = GObject.MainLoop()
2963
2981
    bus = dbus.SystemBus()
2964
2982
    # End of Avahi example code
2965
2983
    if use_dbus:
3334
3352
                sys.exit(1)
3335
3353
            # End of Avahi example code
3336
3354
        
3337
 
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
 
3355
        GObject.io_add_watch(tcp_server.fileno(), GObject.IO_IN,
3338
3356
                             lambda *args, **kwargs:
3339
3357
                             (tcp_server.handle_request
3340
3358
                              (*args[2:], **kwargs) or True))