/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2016-02-28 03:01:43 UTC
  • mto: (237.7.594 trunk)
  • mto: This revision was merged to the branch mainline in revision 331.
  • Revision ID: teddy@recompile.se-20160228030143-i6w90r7wzkvlx9kq
Stop using python-gnutls.  Use GnuTLS 3.3 or later directly.

* INSTALL: Document dependency on GnuTLS 3.3 and remove dependency on
          Python-GnuTLS.

* debian/control (Source: mandos/Build-Depends): Add (>= 3.3.0) to
                                                 "libgnutls28-dev" and
                                                 "gnutls-dev".
  (Source: mandos/Build-Depends-Indep): Remove "python2.7-gnutls".
  (Package: mandos/Depends): Remove "python-gnutls" and
                             "python2.7-gnutls", add "libgnutls28-dev
                             (>= 3.3.0) | libgnutls30 (>= 3.3.0)"
* mandos: Remove imports of "gnutls" and all submodules.
  (GnuTLS, gnutls): New; simulate a "gnutls" module.  Change all
                    callers to match new shorter names.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
 
1
#!/usr/bin/python2.7
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-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
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
 
    from gi.repository import GObject
 
80
    import gobject
81
81
except ImportError:
82
 
    import gobject as GObject
 
82
    from gi.repository 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.3"
 
101
version = "1.7.1"
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
165
154
        self.gnupgargs = ['--batch',
166
 
                          '--homedir', self.tempdir,
 
155
                          '--home', self.tempdir,
167
156
                          '--force-mdc',
168
157
                          '--quiet',
169
158
                          '--no-use-agent']
208
197
                dir=self.tempdir) as passfile:
209
198
            passfile.write(passphrase)
210
199
            passfile.flush()
211
 
            proc = subprocess.Popen([self.gpg, '--symmetric',
 
200
            proc = subprocess.Popen(['gpg', '--symmetric',
212
201
                                     '--passphrase-file',
213
202
                                     passfile.name]
214
203
                                    + self.gnupgargs,
226
215
                dir = self.tempdir) as passfile:
227
216
            passfile.write(passphrase)
228
217
            passfile.flush()
229
 
            proc = subprocess.Popen([self.gpg, '--decrypt',
 
218
            proc = subprocess.Popen(['gpg', '--decrypt',
230
219
                                     '--passphrase-file',
231
220
                                     passfile.name]
232
221
                                    + self.gnupgargs,
461
450
    
462
451
    # Constants
463
452
    E_SUCCESS = 0
464
 
    E_INTERRUPTED = -52
465
 
    E_AGAIN = -28
466
453
    CRT_OPENPGP = 2
467
454
    CLIENT = 2
468
455
    SHUT_RDWR = 0
496
483
        # We need to use the class name "GnuTLS" here, since this
497
484
        # exception might be raised from within GnuTLS.__init__,
498
485
        # which is called before the assignment to the "gnutls"
499
 
        # global variable has happened.
 
486
        # global variable happens.
500
487
        def __init__(self, message = None, code = None, args=()):
501
488
            # Default usage is by a message string, but if a return
502
489
            # code is passed, convert it to a string with
503
490
            # gnutls.strerror()
504
 
            self.code = code
505
491
            if message is None and code is not None:
506
492
                message = GnuTLS.strerror(code)
507
493
            return super(GnuTLS.Error, self).__init__(
545
531
        
546
532
        def send(self, data):
547
533
            data = bytes(data)
548
 
            data_len = len(data)
549
 
            while data_len > 0:
550
 
                data_len -= gnutls.record_send(self._c_object,
551
 
                                               data[-data_len:],
552
 
                                               data_len)
 
534
            if not data:
 
535
                return 0
 
536
            return gnutls.record_send(self._c_object, data, len(data))
553
537
        
554
538
        def bye(self):
555
539
            return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
556
540
    
557
 
    # Error handling functions
 
541
    # Error handling function
558
542
    def _error_code(result):
559
543
        """A function to raise exceptions on errors, suitable
560
544
        for the 'restype' attribute on ctypes functions"""
564
548
            raise gnutls.CertificateSecurityError(code = result)
565
549
        raise gnutls.Error(code = result)
566
550
    
567
 
    def _retry_on_error(result, func, arguments):
568
 
        """A function to retry on some errors, suitable
569
 
        for the 'errcheck' attribute on ctypes functions"""
570
 
        while result < 0:
571
 
            if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
572
 
                return _error_code(result)
573
 
            result = func(*arguments)
574
 
        return result
575
 
    
576
551
    # Unless otherwise indicated, the function declarations below are
577
552
    # all from the gnutls/gnutls.h C header file.
578
553
    
594
569
    record_send.argtypes = [session_t, ctypes.c_void_p,
595
570
                            ctypes.c_size_t]
596
571
    record_send.restype = ctypes.c_ssize_t
597
 
    record_send.errcheck = _retry_on_error
598
572
    
599
573
    certificate_allocate_credentials = (
600
574
        _library.gnutls_certificate_allocate_credentials)
646
620
    handshake = _library.gnutls_handshake
647
621
    handshake.argtypes = [session_t]
648
622
    handshake.restype = _error_code
649
 
    handshake.errcheck = _retry_on_error
650
623
    
651
624
    transport_set_ptr = _library.gnutls_transport_set_ptr
652
625
    transport_set_ptr.argtypes = [session_t, transport_ptr_t]
655
628
    bye = _library.gnutls_bye
656
629
    bye.argtypes = [session_t, close_request_t]
657
630
    bye.restype = _error_code
658
 
    bye.errcheck = _retry_on_error
659
631
    
660
632
    check_version = _library.gnutls_check_version
661
633
    check_version.argtypes = [ctypes.c_char_p]
690
662
                                                ctypes.c_size_t)]
691
663
    openpgp_crt_get_fingerprint.restype = _error_code
692
664
    
693
 
    # Remove non-public functions
694
 
    del _error_code, _retry_on_error
 
665
    # Remove non-public function
 
666
    del _error_code
695
667
# Create the global "gnutls" object, simulating a module
696
668
gnutls = GnuTLS()
697
669
 
715
687
    checker:    subprocess.Popen(); a running checker process used
716
688
                                    to see if the client lives.
717
689
                                    'None' if no process is running.
718
 
    checker_callback_tag: a GObject event source tag, or None
 
690
    checker_callback_tag: a gobject event source tag, or None
719
691
    checker_command: string; External command which is run to check
720
692
                     if client lives.  %() expansions are done at
721
693
                     runtime with vars(self) as dict, so that for
722
694
                     instance %(name)s can be used in the command.
723
 
    checker_initiator_tag: a GObject event source tag, or None
 
695
    checker_initiator_tag: a gobject event source tag, or None
724
696
    created:    datetime.datetime(); (UTC) object creation
725
697
    client_structure: Object describing what attributes a client has
726
698
                      and is used for storing the client at exit
727
699
    current_checker_command: string; current running checker_command
728
 
    disable_initiator_tag: a GObject event source tag, or None
 
700
    disable_initiator_tag: a gobject event source tag, or None
729
701
    enabled:    bool()
730
702
    fingerprint: string (40 or 32 hexadecimal digits); used to
731
703
                 uniquely identify the client
885
857
        if not quiet:
886
858
            logger.info("Disabling client %s", self.name)
887
859
        if getattr(self, "disable_initiator_tag", None) is not None:
888
 
            GObject.source_remove(self.disable_initiator_tag)
 
860
            gobject.source_remove(self.disable_initiator_tag)
889
861
            self.disable_initiator_tag = None
890
862
        self.expires = None
891
863
        if getattr(self, "checker_initiator_tag", None) is not None:
892
 
            GObject.source_remove(self.checker_initiator_tag)
 
864
            gobject.source_remove(self.checker_initiator_tag)
893
865
            self.checker_initiator_tag = None
894
866
        self.stop_checker()
895
867
        self.enabled = False
896
868
        if not quiet:
897
869
            self.send_changedstate()
898
 
        # Do not run this again if called by a GObject.timeout_add
 
870
        # Do not run this again if called by a gobject.timeout_add
899
871
        return False
900
872
    
901
873
    def __del__(self):
905
877
        # Schedule a new checker to be started an 'interval' from now,
906
878
        # and every interval from then on.
907
879
        if self.checker_initiator_tag is not None:
908
 
            GObject.source_remove(self.checker_initiator_tag)
909
 
        self.checker_initiator_tag = GObject.timeout_add(
 
880
            gobject.source_remove(self.checker_initiator_tag)
 
881
        self.checker_initiator_tag = gobject.timeout_add(
910
882
            int(self.interval.total_seconds() * 1000),
911
883
            self.start_checker)
912
884
        # Schedule a disable() when 'timeout' has passed
913
885
        if self.disable_initiator_tag is not None:
914
 
            GObject.source_remove(self.disable_initiator_tag)
915
 
        self.disable_initiator_tag = GObject.timeout_add(
 
886
            gobject.source_remove(self.disable_initiator_tag)
 
887
        self.disable_initiator_tag = gobject.timeout_add(
916
888
            int(self.timeout.total_seconds() * 1000), self.disable)
917
889
        # Also start a new checker *right now*.
918
890
        self.start_checker()
954
926
        if timeout is None:
955
927
            timeout = self.timeout
956
928
        if self.disable_initiator_tag is not None:
957
 
            GObject.source_remove(self.disable_initiator_tag)
 
929
            gobject.source_remove(self.disable_initiator_tag)
958
930
            self.disable_initiator_tag = None
959
931
        if getattr(self, "enabled", False):
960
 
            self.disable_initiator_tag = GObject.timeout_add(
 
932
            self.disable_initiator_tag = gobject.timeout_add(
961
933
                int(timeout.total_seconds() * 1000), self.disable)
962
934
            self.expires = datetime.datetime.utcnow() + timeout
963
935
    
1018
990
                args = (pipe[1], subprocess.call, command),
1019
991
                kwargs = popen_args)
1020
992
            self.checker.start()
1021
 
            self.checker_callback_tag = GObject.io_add_watch(
1022
 
                pipe[0].fileno(), GObject.IO_IN,
 
993
            self.checker_callback_tag = gobject.io_add_watch(
 
994
                pipe[0].fileno(), gobject.IO_IN,
1023
995
                self.checker_callback, pipe[0], command)
1024
 
        # Re-run this periodically if run by GObject.timeout_add
 
996
        # Re-run this periodically if run by gobject.timeout_add
1025
997
        return True
1026
998
    
1027
999
    def stop_checker(self):
1028
1000
        """Force the checker process, if any, to stop."""
1029
1001
        if self.checker_callback_tag:
1030
 
            GObject.source_remove(self.checker_callback_tag)
 
1002
            gobject.source_remove(self.checker_callback_tag)
1031
1003
            self.checker_callback_tag = None
1032
1004
        if getattr(self, "checker", None) is None:
1033
1005
            return
1807
1779
    
1808
1780
    def approve(self, value=True):
1809
1781
        self.approved = value
1810
 
        GObject.timeout_add(int(self.approval_duration.total_seconds()
 
1782
        gobject.timeout_add(int(self.approval_duration.total_seconds()
1811
1783
                                * 1000), self._reset_approved)
1812
1784
        self.send_changedstate()
1813
1785
    
2024
1996
                if (getattr(self, "disable_initiator_tag", None)
2025
1997
                    is None):
2026
1998
                    return
2027
 
                GObject.source_remove(self.disable_initiator_tag)
2028
 
                self.disable_initiator_tag = GObject.timeout_add(
 
1999
                gobject.source_remove(self.disable_initiator_tag)
 
2000
                self.disable_initiator_tag = gobject.timeout_add(
2029
2001
                    int((self.expires - now).total_seconds() * 1000),
2030
2002
                    self.disable)
2031
2003
    
2051
2023
            return
2052
2024
        if self.enabled:
2053
2025
            # Reschedule checker run
2054
 
            GObject.source_remove(self.checker_initiator_tag)
2055
 
            self.checker_initiator_tag = GObject.timeout_add(
 
2026
            gobject.source_remove(self.checker_initiator_tag)
 
2027
            self.checker_initiator_tag = gobject.timeout_add(
2056
2028
                value, self.start_checker)
2057
2029
            self.start_checker() # Start one now, too
2058
2030
    
2243
2215
                    else:
2244
2216
                        delay -= time2 - time
2245
2217
                
2246
 
                try:
2247
 
                    session.send(client.secret)
2248
 
                except gnutls.Error as error:
2249
 
                    logger.warning("gnutls send failed",
2250
 
                                   exc_info = error)
2251
 
                    return
 
2218
                sent_size = 0
 
2219
                while sent_size < len(client.secret):
 
2220
                    try:
 
2221
                        sent = session.send(client.secret[sent_size:])
 
2222
                    except gnutls.Error as error:
 
2223
                        logger.warning("gnutls send failed",
 
2224
                                       exc_info=error)
 
2225
                        return
 
2226
                    logger.debug("Sent: %d, remaining: %d", sent,
 
2227
                                 len(client.secret) - (sent_size
 
2228
                                                       + sent))
 
2229
                    sent_size += sent
2252
2230
                
2253
2231
                logger.info("Sending secret to %s", client.name)
2254
2232
                # bump the timeout using extended_timeout
2462
2440
        gnutls_priority GnuTLS priority string
2463
2441
        use_dbus:       Boolean; to emit D-Bus signals or not
2464
2442
    
2465
 
    Assumes a GObject.MainLoop event loop.
 
2443
    Assumes a gobject.MainLoop event loop.
2466
2444
    """
2467
2445
    
2468
2446
    def __init__(self, server_address, RequestHandlerClass,
2493
2471
    
2494
2472
    def add_pipe(self, parent_pipe, proc):
2495
2473
        # Call "handle_ipc" for both data and EOF events
2496
 
        GObject.io_add_watch(
 
2474
        gobject.io_add_watch(
2497
2475
            parent_pipe.fileno(),
2498
 
            GObject.IO_IN | GObject.IO_HUP,
 
2476
            gobject.IO_IN | gobject.IO_HUP,
2499
2477
            functools.partial(self.handle_ipc,
2500
2478
                              parent_pipe = parent_pipe,
2501
2479
                              proc = proc))
2505
2483
                   proc = None,
2506
2484
                   client_object=None):
2507
2485
        # error, or the other end of multiprocessing.Pipe has closed
2508
 
        if condition & (GObject.IO_ERR | GObject.IO_HUP):
 
2486
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
2509
2487
            # Wait for other process to exit
2510
2488
            proc.join()
2511
2489
            return False
2532
2510
                parent_pipe.send(False)
2533
2511
                return False
2534
2512
            
2535
 
            GObject.io_add_watch(
 
2513
            gobject.io_add_watch(
2536
2514
                parent_pipe.fileno(),
2537
 
                GObject.IO_IN | GObject.IO_HUP,
 
2515
                gobject.IO_IN | gobject.IO_HUP,
2538
2516
                functools.partial(self.handle_ipc,
2539
2517
                                  parent_pipe = parent_pipe,
2540
2518
                                  proc = proc,
2922
2900
            logger.error("Could not open file %r", pidfilename,
2923
2901
                         exc_info=e)
2924
2902
    
2925
 
    for name, group in (("_mandos", "_mandos"),
2926
 
                        ("mandos", "mandos"),
2927
 
                        ("nobody", "nogroup")):
 
2903
    for name in ("_mandos", "mandos", "nobody"):
2928
2904
        try:
2929
2905
            uid = pwd.getpwnam(name).pw_uid
2930
 
            gid = pwd.getpwnam(group).pw_gid
 
2906
            gid = pwd.getpwnam(name).pw_gid
2931
2907
            break
2932
2908
        except KeyError:
2933
2909
            continue
2965
2941
        # Close all input and output, do double fork, etc.
2966
2942
        daemon()
2967
2943
    
2968
 
    # multiprocessing will use threads, so before we use GObject we
2969
 
    # need to inform GObject that threads will be used.
2970
 
    GObject.threads_init()
 
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()
2971
2947
    
2972
2948
    global main_loop
2973
2949
    # From the Avahi example code
2974
2950
    DBusGMainLoop(set_as_default=True)
2975
 
    main_loop = GObject.MainLoop()
 
2951
    main_loop = gobject.MainLoop()
2976
2952
    bus = dbus.SystemBus()
2977
2953
    # End of Avahi example code
2978
2954
    if use_dbus:
3347
3323
                sys.exit(1)
3348
3324
            # End of Avahi example code
3349
3325
        
3350
 
        GObject.io_add_watch(tcp_server.fileno(), GObject.IO_IN,
 
3326
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3351
3327
                             lambda *args, **kwargs:
3352
3328
                             (tcp_server.handle_request
3353
3329
                              (*args[2:], **kwargs) or True))