/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: 2012-06-24 03:45:55 UTC
  • Revision ID: teddy@recompile.se-20120624034555-7p0dtbpsz0dgsn4d
* mandos (Client.start_checker): Add comment.  Break long line.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2013 Teddy Hogeborn
15
 
# Copyright © 2008-2013 Björn Påhlsson
 
14
# Copyright © 2008-2012 Teddy Hogeborn
 
15
# Copyright © 2008-2012 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
79
79
import ctypes.util
80
80
import xml.dom.minidom
81
81
import inspect
 
82
import GnuPGInterface
82
83
 
83
84
try:
84
85
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
88
89
    except ImportError:
89
90
        SO_BINDTODEVICE = None
90
91
 
91
 
version = "1.6.2"
 
92
version = "1.6.0"
92
93
stored_state_file = "clients.pickle"
93
94
 
94
95
logger = logging.getLogger()
139
140
class PGPEngine(object):
140
141
    """A simple class for OpenPGP symmetric encryption & decryption"""
141
142
    def __init__(self):
 
143
        self.gnupg = GnuPGInterface.GnuPG()
142
144
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
143
 
        self.gnupgargs = ['--batch',
144
 
                          '--home', self.tempdir,
145
 
                          '--force-mdc',
146
 
                          '--quiet',
147
 
                          '--no-use-agent']
 
145
        self.gnupg = GnuPGInterface.GnuPG()
 
146
        self.gnupg.options.meta_interactive = False
 
147
        self.gnupg.options.homedir = self.tempdir
 
148
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
149
                                              '--quiet',
 
150
                                              '--no-use-agent'])
148
151
    
149
152
    def __enter__(self):
150
153
        return self
172
175
    def password_encode(self, password):
173
176
        # Passphrase can not be empty and can not contain newlines or
174
177
        # NUL bytes.  So we prefix it and hex encode it.
175
 
        encoded = b"mandos" + binascii.hexlify(password)
176
 
        if len(encoded) > 2048:
177
 
            # GnuPG can't handle long passwords, so encode differently
178
 
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
179
 
                       .replace(b"\n", b"\\n")
180
 
                       .replace(b"\0", b"\\x00"))
181
 
        return encoded
 
178
        return b"mandos" + binascii.hexlify(password)
182
179
    
183
180
    def encrypt(self, data, password):
184
 
        passphrase = self.password_encode(password)
185
 
        with tempfile.NamedTemporaryFile(dir=self.tempdir
186
 
                                         ) as passfile:
187
 
            passfile.write(passphrase)
188
 
            passfile.flush()
189
 
            proc = subprocess.Popen(['gpg', '--symmetric',
190
 
                                     '--passphrase-file',
191
 
                                     passfile.name]
192
 
                                    + self.gnupgargs,
193
 
                                    stdin = subprocess.PIPE,
194
 
                                    stdout = subprocess.PIPE,
195
 
                                    stderr = subprocess.PIPE)
196
 
            ciphertext, err = proc.communicate(input = data)
197
 
        if proc.returncode != 0:
198
 
            raise PGPError(err)
 
181
        self.gnupg.passphrase = self.password_encode(password)
 
182
        with open(os.devnull, "w") as devnull:
 
183
            try:
 
184
                proc = self.gnupg.run(['--symmetric'],
 
185
                                      create_fhs=['stdin', 'stdout'],
 
186
                                      attach_fhs={'stderr': devnull})
 
187
                with contextlib.closing(proc.handles['stdin']) as f:
 
188
                    f.write(data)
 
189
                with contextlib.closing(proc.handles['stdout']) as f:
 
190
                    ciphertext = f.read()
 
191
                proc.wait()
 
192
            except IOError as e:
 
193
                raise PGPError(e)
 
194
        self.gnupg.passphrase = None
199
195
        return ciphertext
200
196
    
201
197
    def decrypt(self, data, password):
202
 
        passphrase = self.password_encode(password)
203
 
        with tempfile.NamedTemporaryFile(dir = self.tempdir
204
 
                                         ) as passfile:
205
 
            passfile.write(passphrase)
206
 
            passfile.flush()
207
 
            proc = subprocess.Popen(['gpg', '--decrypt',
208
 
                                     '--passphrase-file',
209
 
                                     passfile.name]
210
 
                                    + self.gnupgargs,
211
 
                                    stdin = subprocess.PIPE,
212
 
                                    stdout = subprocess.PIPE,
213
 
                                    stderr = subprocess.PIPE)
214
 
            decrypted_plaintext, err = proc.communicate(input
215
 
                                                        = data)
216
 
        if proc.returncode != 0:
217
 
            raise PGPError(err)
 
198
        self.gnupg.passphrase = self.password_encode(password)
 
199
        with open(os.devnull, "w") as devnull:
 
200
            try:
 
201
                proc = self.gnupg.run(['--decrypt'],
 
202
                                      create_fhs=['stdin', 'stdout'],
 
203
                                      attach_fhs={'stderr': devnull})
 
204
                with contextlib.closing(proc.handles['stdin']) as f:
 
205
                    f.write(data)
 
206
                with contextlib.closing(proc.handles['stdout']) as f:
 
207
                    decrypted_plaintext = f.read()
 
208
                proc.wait()
 
209
            except IOError as e:
 
210
                raise PGPError(e)
 
211
        self.gnupg.passphrase = None
218
212
        return decrypted_plaintext
219
213
 
220
214
 
240
234
               Used to optionally bind to the specified interface.
241
235
    name: string; Example: 'Mandos'
242
236
    type: string; Example: '_mandos._tcp'.
243
 
     See <https://www.iana.org/assignments/service-names-port-numbers>
 
237
                  See <http://www.dns-sd.org/ServiceTypes.html>
244
238
    port: integer; what port to announce
245
239
    TXT: list of strings; TXT record for the service
246
240
    domain: string; Domain to publish on, default to .local if empty.
446
440
    runtime_expansions: Allowed attributes for runtime expansion.
447
441
    expires:    datetime.datetime(); time (UTC) when a client will be
448
442
                disabled, or None
449
 
    server_settings: The server_settings dict from main()
450
443
    """
451
444
    
452
445
    runtime_expansions = ("approval_delay", "approval_duration",
527
520
        
528
521
        return settings
529
522
    
530
 
    def __init__(self, settings, name = None, server_settings=None):
 
523
    def __init__(self, settings, name = None):
531
524
        self.name = name
532
 
        if server_settings is None:
533
 
            server_settings = {}
534
 
        self.server_settings = server_settings
535
525
        # adding all client settings
536
526
        for setting, value in settings.iteritems():
537
527
            setattr(self, setting, value)
690
680
        # If a checker exists, make sure it is not a zombie
691
681
        try:
692
682
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
693
 
        except AttributeError:
694
 
            pass
695
 
        except OSError as error:
696
 
            if error.errno != errno.ECHILD:
697
 
                raise
 
683
        except (AttributeError, OSError) as error:
 
684
            if (isinstance(error, OSError)
 
685
                and error.errno != errno.ECHILD):
 
686
                raise error
698
687
        else:
699
688
            if pid:
700
689
                logger.warning("Checker was a zombie")
722
711
                # in normal mode, that is already done by daemon(),
723
712
                # and in debug mode we don't want to.  (Stdin is
724
713
                # always replaced by /dev/null.)
725
 
                # The exception is when not debugging but nevertheless
726
 
                # running in the foreground; use the previously
727
 
                # created wnull.
728
 
                popen_args = {}
729
 
                if (not self.server_settings["debug"]
730
 
                    and self.server_settings["foreground"]):
731
 
                    popen_args.update({"stdout": wnull,
732
 
                                       "stderr": wnull })
733
714
                self.checker = subprocess.Popen(command,
734
715
                                                close_fds=True,
735
 
                                                shell=True, cwd="/",
736
 
                                                **popen_args)
 
716
                                                shell=True, cwd="/")
737
717
            except OSError as error:
738
718
                logger.error("Failed to start subprocess",
739
719
                             exc_info=error)
1098
1078
                interface_names.add(alt_interface)
1099
1079
                # Is this a D-Bus signal?
1100
1080
                if getattr(attribute, "_dbus_is_signal", False):
1101
 
                    # Extract the original non-method undecorated
1102
 
                    # function by black magic
 
1081
                    # Extract the original non-method function by
 
1082
                    # black magic
1103
1083
                    nonmethod_func = (dict(
1104
1084
                            zip(attribute.func_code.co_freevars,
1105
1085
                                attribute.__closure__))["func"]
1703
1683
            logger.debug("Protocol version: %r", line)
1704
1684
            try:
1705
1685
                if int(line.strip().split()[0]) > 1:
1706
 
                    raise RuntimeError(line)
 
1686
                    raise RuntimeError
1707
1687
            except (ValueError, IndexError, RuntimeError) as error:
1708
1688
                logger.error("Unknown protocol version: %s", error)
1709
1689
                return
1916
1896
    
1917
1897
    def add_pipe(self, parent_pipe, proc):
1918
1898
        """Dummy function; override as necessary"""
1919
 
        raise NotImplementedError()
 
1899
        raise NotImplementedError
1920
1900
 
1921
1901
 
1922
1902
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1998
1978
                if self.address_family == socket.AF_INET6:
1999
1979
                    any_address = "::" # in6addr_any
2000
1980
                else:
2001
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
1981
                    any_address = socket.INADDR_ANY
2002
1982
                self.server_address = (any_address,
2003
1983
                                       self.server_address[1])
2004
1984
            elif not self.server_address[1]:
2259
2239
            else:
2260
2240
                raise ValueError("Unknown suffix {0!r}"
2261
2241
                                 .format(suffix))
2262
 
        except IndexError as e:
 
2242
        except (ValueError, IndexError) as e:
2263
2243
            raise ValueError(*(e.args))
2264
2244
        timevalue += delta
2265
2245
    return timevalue
2309
2289
                        help="Run self-test")
2310
2290
    parser.add_argument("--debug", action="store_true",
2311
2291
                        help="Debug mode; run in foreground and log"
2312
 
                        " to terminal", default=None)
 
2292
                        " to terminal")
2313
2293
    parser.add_argument("--debuglevel", metavar="LEVEL",
2314
2294
                        help="Debug level for stdout output")
2315
2295
    parser.add_argument("--priority", help="GnuTLS"
2322
2302
                        " files")
2323
2303
    parser.add_argument("--no-dbus", action="store_false",
2324
2304
                        dest="use_dbus", help="Do not provide D-Bus"
2325
 
                        " system bus interface", default=None)
 
2305
                        " system bus interface")
2326
2306
    parser.add_argument("--no-ipv6", action="store_false",
2327
 
                        dest="use_ipv6", help="Do not use IPv6",
2328
 
                        default=None)
 
2307
                        dest="use_ipv6", help="Do not use IPv6")
2329
2308
    parser.add_argument("--no-restore", action="store_false",
2330
2309
                        dest="restore", help="Do not restore stored"
2331
 
                        " state", default=None)
 
2310
                        " state")
2332
2311
    parser.add_argument("--socket", type=int,
2333
2312
                        help="Specify a file descriptor to a network"
2334
2313
                        " socket to use instead of creating one")
2335
2314
    parser.add_argument("--statedir", metavar="DIR",
2336
2315
                        help="Directory to save/restore state in")
2337
2316
    parser.add_argument("--foreground", action="store_true",
2338
 
                        help="Run in foreground", default=None)
 
2317
                        help="Run in foreground")
2339
2318
    
2340
2319
    options = parser.parse_args()
2341
2320
    
2350
2329
                        "port": "",
2351
2330
                        "debug": "False",
2352
2331
                        "priority":
2353
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2332
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2354
2333
                        "servicename": "Mandos",
2355
2334
                        "use_dbus": "True",
2356
2335
                        "use_ipv6": "True",
2400
2379
    for option in server_settings.keys():
2401
2380
        if type(server_settings[option]) is str:
2402
2381
            server_settings[option] = unicode(server_settings[option])
2403
 
    # Force all boolean options to be boolean
2404
 
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2405
 
                   "foreground"):
2406
 
        server_settings[option] = bool(server_settings[option])
2407
2382
    # Debug implies foreground
2408
2383
    if server_settings["debug"]:
2409
2384
        server_settings["foreground"] = True
2457
2432
                              socketfd=(server_settings["socket"]
2458
2433
                                        or None))
2459
2434
    if not foreground:
2460
 
        pidfilename = "/run/mandos.pid"
2461
 
        if not os.path.isdir("/run/."):
2462
 
            pidfilename = "/var/run/mandos.pid"
 
2435
        pidfilename = "/var/run/mandos.pid"
2463
2436
        pidfile = None
2464
2437
        try:
2465
2438
            pidfile = open(pidfilename, "w")
2482
2455
        os.setuid(uid)
2483
2456
    except OSError as error:
2484
2457
        if error.errno != errno.EPERM:
2485
 
            raise
 
2458
            raise error
2486
2459
    
2487
2460
    if debug:
2488
2461
        # Enable all possible GnuTLS debugging
2551
2524
    old_client_settings = {}
2552
2525
    clients_data = {}
2553
2526
    
2554
 
    # This is used to redirect stdout and stderr for checker processes
2555
 
    global wnull
2556
 
    wnull = open(os.devnull, "w") # A writable /dev/null
2557
 
    # Only used if server is running in foreground but not in debug
2558
 
    # mode
2559
 
    if debug or not foreground:
2560
 
        wnull.close()
2561
 
    
2562
2527
    # Get client data and settings from last running state.
2563
2528
    if server_settings["restore"]:
2564
2529
        try:
2580
2545
    
2581
2546
    with PGPEngine() as pgp:
2582
2547
        for client_name, client in clients_data.iteritems():
2583
 
            # Skip removed clients
2584
 
            if client_name not in client_settings:
2585
 
                continue
2586
 
            
2587
2548
            # Decide which value to use after restoring saved state.
2588
2549
            # We have three different values: Old config file,
2589
2550
            # new config file, and saved state.
2651
2612
    # Create all client objects
2652
2613
    for client_name, client in clients_data.iteritems():
2653
2614
        tcp_server.clients[client_name] = client_class(
2654
 
            name = client_name, settings = client,
2655
 
            server_settings = server_settings)
 
2615
            name = client_name, settings = client)
2656
2616
    
2657
2617
    if not tcp_server.clients:
2658
2618
        logger.warning("No clients defined")
2741
2701
        service.cleanup()
2742
2702
        
2743
2703
        multiprocessing.active_children()
2744
 
        wnull.close()
2745
2704
        if not (tcp_server.clients or client_settings):
2746
2705
            return
2747
2706
        
2759
2718
                # A list of attributes that can not be pickled
2760
2719
                # + secret.
2761
2720
                exclude = set(("bus", "changedstate", "secret",
2762
 
                               "checker", "server_settings"))
 
2721
                               "checker"))
2763
2722
                for name, typ in (inspect.getmembers
2764
2723
                                  (dbus.service.Object)):
2765
2724
                    exclude.add(name)
2793
2752
            else:
2794
2753
                logger.warning("Could not save persistent state:",
2795
2754
                               exc_info=e)
2796
 
                raise
 
2755
                raise e
2797
2756
        
2798
2757
        # Delete all clients, and settings from config
2799
2758
        while tcp_server.clients: