/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-02-28 10:59:18 UTC
  • Revision ID: teddy@recompile.se-20160228105918-tb8pt2p5j0tkcls3
Handle GnuTLS errors and partial sends in gnutls "module".

* mandos (GnuTLS.E_INTERRUPTED, GnuTLS.E_AGAIN): New.
  (GnuTLS.Error): Set error code as "code" attribute.
  (GnuTLS.ClientSession.send): Handle partial sends with a loop.
  (GnuTLS._retry_on_error): New function.
  (GnuTLS.record_send, GnuTLS.handshake, GnuTLS.bye): Set "errcheck"
                                                      attribute to
                                                    "_retry_on_error".
  (ClientHandler.handle): Remove loop for handling partial sends;
                          GnuTLS.ClientSession.send() will do that.

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,
715
704
    checker:    subprocess.Popen(); a running checker process used
716
705
                                    to see if the client lives.
717
706
                                    'None' if no process is running.
718
 
    checker_callback_tag: a GObject event source tag, or None
 
707
    checker_callback_tag: a gobject event source tag, or None
719
708
    checker_command: string; External command which is run to check
720
709
                     if client lives.  %() expansions are done at
721
710
                     runtime with vars(self) as dict, so that for
722
711
                     instance %(name)s can be used in the command.
723
 
    checker_initiator_tag: a GObject event source tag, or None
 
712
    checker_initiator_tag: a gobject event source tag, or None
724
713
    created:    datetime.datetime(); (UTC) object creation
725
714
    client_structure: Object describing what attributes a client has
726
715
                      and is used for storing the client at exit
727
716
    current_checker_command: string; current running checker_command
728
 
    disable_initiator_tag: a GObject event source tag, or None
 
717
    disable_initiator_tag: a gobject event source tag, or None
729
718
    enabled:    bool()
730
719
    fingerprint: string (40 or 32 hexadecimal digits); used to
731
720
                 uniquely identify the client
885
874
        if not quiet:
886
875
            logger.info("Disabling client %s", self.name)
887
876
        if getattr(self, "disable_initiator_tag", None) is not None:
888
 
            GObject.source_remove(self.disable_initiator_tag)
 
877
            gobject.source_remove(self.disable_initiator_tag)
889
878
            self.disable_initiator_tag = None
890
879
        self.expires = None
891
880
        if getattr(self, "checker_initiator_tag", None) is not None:
892
 
            GObject.source_remove(self.checker_initiator_tag)
 
881
            gobject.source_remove(self.checker_initiator_tag)
893
882
            self.checker_initiator_tag = None
894
883
        self.stop_checker()
895
884
        self.enabled = False
896
885
        if not quiet:
897
886
            self.send_changedstate()
898
 
        # Do not run this again if called by a GObject.timeout_add
 
887
        # Do not run this again if called by a gobject.timeout_add
899
888
        return False
900
889
    
901
890
    def __del__(self):
905
894
        # Schedule a new checker to be started an 'interval' from now,
906
895
        # and every interval from then on.
907
896
        if self.checker_initiator_tag is not None:
908
 
            GObject.source_remove(self.checker_initiator_tag)
909
 
        self.checker_initiator_tag = GObject.timeout_add(
 
897
            gobject.source_remove(self.checker_initiator_tag)
 
898
        self.checker_initiator_tag = gobject.timeout_add(
910
899
            int(self.interval.total_seconds() * 1000),
911
900
            self.start_checker)
912
901
        # Schedule a disable() when 'timeout' has passed
913
902
        if self.disable_initiator_tag is not None:
914
 
            GObject.source_remove(self.disable_initiator_tag)
915
 
        self.disable_initiator_tag = GObject.timeout_add(
 
903
            gobject.source_remove(self.disable_initiator_tag)
 
904
        self.disable_initiator_tag = gobject.timeout_add(
916
905
            int(self.timeout.total_seconds() * 1000), self.disable)
917
906
        # Also start a new checker *right now*.
918
907
        self.start_checker()
954
943
        if timeout is None:
955
944
            timeout = self.timeout
956
945
        if self.disable_initiator_tag is not None:
957
 
            GObject.source_remove(self.disable_initiator_tag)
 
946
            gobject.source_remove(self.disable_initiator_tag)
958
947
            self.disable_initiator_tag = None
959
948
        if getattr(self, "enabled", False):
960
 
            self.disable_initiator_tag = GObject.timeout_add(
 
949
            self.disable_initiator_tag = gobject.timeout_add(
961
950
                int(timeout.total_seconds() * 1000), self.disable)
962
951
            self.expires = datetime.datetime.utcnow() + timeout
963
952
    
1018
1007
                args = (pipe[1], subprocess.call, command),
1019
1008
                kwargs = popen_args)
1020
1009
            self.checker.start()
1021
 
            self.checker_callback_tag = GObject.io_add_watch(
1022
 
                pipe[0].fileno(), GObject.IO_IN,
 
1010
            self.checker_callback_tag = gobject.io_add_watch(
 
1011
                pipe[0].fileno(), gobject.IO_IN,
1023
1012
                self.checker_callback, pipe[0], command)
1024
 
        # Re-run this periodically if run by GObject.timeout_add
 
1013
        # Re-run this periodically if run by gobject.timeout_add
1025
1014
        return True
1026
1015
    
1027
1016
    def stop_checker(self):
1028
1017
        """Force the checker process, if any, to stop."""
1029
1018
        if self.checker_callback_tag:
1030
 
            GObject.source_remove(self.checker_callback_tag)
 
1019
            gobject.source_remove(self.checker_callback_tag)
1031
1020
            self.checker_callback_tag = None
1032
1021
        if getattr(self, "checker", None) is None:
1033
1022
            return
1807
1796
    
1808
1797
    def approve(self, value=True):
1809
1798
        self.approved = value
1810
 
        GObject.timeout_add(int(self.approval_duration.total_seconds()
 
1799
        gobject.timeout_add(int(self.approval_duration.total_seconds()
1811
1800
                                * 1000), self._reset_approved)
1812
1801
        self.send_changedstate()
1813
1802
    
2024
2013
                if (getattr(self, "disable_initiator_tag", None)
2025
2014
                    is None):
2026
2015
                    return
2027
 
                GObject.source_remove(self.disable_initiator_tag)
2028
 
                self.disable_initiator_tag = GObject.timeout_add(
 
2016
                gobject.source_remove(self.disable_initiator_tag)
 
2017
                self.disable_initiator_tag = gobject.timeout_add(
2029
2018
                    int((self.expires - now).total_seconds() * 1000),
2030
2019
                    self.disable)
2031
2020
    
2051
2040
            return
2052
2041
        if self.enabled:
2053
2042
            # Reschedule checker run
2054
 
            GObject.source_remove(self.checker_initiator_tag)
2055
 
            self.checker_initiator_tag = GObject.timeout_add(
 
2043
            gobject.source_remove(self.checker_initiator_tag)
 
2044
            self.checker_initiator_tag = gobject.timeout_add(
2056
2045
                value, self.start_checker)
2057
2046
            self.start_checker() # Start one now, too
2058
2047
    
2462
2451
        gnutls_priority GnuTLS priority string
2463
2452
        use_dbus:       Boolean; to emit D-Bus signals or not
2464
2453
    
2465
 
    Assumes a GObject.MainLoop event loop.
 
2454
    Assumes a gobject.MainLoop event loop.
2466
2455
    """
2467
2456
    
2468
2457
    def __init__(self, server_address, RequestHandlerClass,
2493
2482
    
2494
2483
    def add_pipe(self, parent_pipe, proc):
2495
2484
        # Call "handle_ipc" for both data and EOF events
2496
 
        GObject.io_add_watch(
 
2485
        gobject.io_add_watch(
2497
2486
            parent_pipe.fileno(),
2498
 
            GObject.IO_IN | GObject.IO_HUP,
 
2487
            gobject.IO_IN | gobject.IO_HUP,
2499
2488
            functools.partial(self.handle_ipc,
2500
2489
                              parent_pipe = parent_pipe,
2501
2490
                              proc = proc))
2505
2494
                   proc = None,
2506
2495
                   client_object=None):
2507
2496
        # error, or the other end of multiprocessing.Pipe has closed
2508
 
        if condition & (GObject.IO_ERR | GObject.IO_HUP):
 
2497
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
2509
2498
            # Wait for other process to exit
2510
2499
            proc.join()
2511
2500
            return False
2532
2521
                parent_pipe.send(False)
2533
2522
                return False
2534
2523
            
2535
 
            GObject.io_add_watch(
 
2524
            gobject.io_add_watch(
2536
2525
                parent_pipe.fileno(),
2537
 
                GObject.IO_IN | GObject.IO_HUP,
 
2526
                gobject.IO_IN | gobject.IO_HUP,
2538
2527
                functools.partial(self.handle_ipc,
2539
2528
                                  parent_pipe = parent_pipe,
2540
2529
                                  proc = proc,
2922
2911
            logger.error("Could not open file %r", pidfilename,
2923
2912
                         exc_info=e)
2924
2913
    
2925
 
    for name, group in (("_mandos", "_mandos"),
2926
 
                        ("mandos", "mandos"),
2927
 
                        ("nobody", "nogroup")):
 
2914
    for name in ("_mandos", "mandos", "nobody"):
2928
2915
        try:
2929
2916
            uid = pwd.getpwnam(name).pw_uid
2930
 
            gid = pwd.getpwnam(group).pw_gid
 
2917
            gid = pwd.getpwnam(name).pw_gid
2931
2918
            break
2932
2919
        except KeyError:
2933
2920
            continue
2965
2952
        # Close all input and output, do double fork, etc.
2966
2953
        daemon()
2967
2954
    
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()
 
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()
2971
2958
    
2972
2959
    global main_loop
2973
2960
    # From the Avahi example code
2974
2961
    DBusGMainLoop(set_as_default=True)
2975
 
    main_loop = GObject.MainLoop()
 
2962
    main_loop = gobject.MainLoop()
2976
2963
    bus = dbus.SystemBus()
2977
2964
    # End of Avahi example code
2978
2965
    if use_dbus:
3347
3334
                sys.exit(1)
3348
3335
            # End of Avahi example code
3349
3336
        
3350
 
        GObject.io_add_watch(tcp_server.fileno(), GObject.IO_IN,
 
3337
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
3351
3338
                             lambda *args, **kwargs:
3352
3339
                             (tcp_server.handle_request
3353
3340
                              (*args[2:], **kwargs) or True))