/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-10-24 19:34:13 UTC
  • Revision ID: teddy@recompile.se-20121024193413-yiorm0x5lj48fu7i
* mandos: Comment changes.
* mandos-keygen (--password): Fix bashism.  Thanks to Raphael Geissert
                              <atomo64@gmail.com> for the bug report.

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-2014 Teddy Hogeborn
15
 
# Copyright © 2008-2014 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.5"
 
92
version = "1.6.0"
92
93
stored_state_file = "clients.pickle"
93
94
 
94
95
logger = logging.getLogger()
95
 
syslogger = None
 
96
syslogger = (logging.handlers.SysLogHandler
 
97
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
98
              address = str("/dev/log")))
96
99
 
97
100
try:
98
101
    if_nametoindex = (ctypes.cdll.LoadLibrary
114
117
def initlogger(debug, level=logging.WARNING):
115
118
    """init logger and add loglevel"""
116
119
    
117
 
    syslogger = (logging.handlers.SysLogHandler
118
 
                 (facility =
119
 
                  logging.handlers.SysLogHandler.LOG_DAEMON,
120
 
                  address = str("/dev/log")))
121
120
    syslogger.setFormatter(logging.Formatter
122
121
                           ('Mandos [%(process)d]: %(levelname)s:'
123
122
                            ' %(message)s'))
141
140
class PGPEngine(object):
142
141
    """A simple class for OpenPGP symmetric encryption & decryption"""
143
142
    def __init__(self):
 
143
        self.gnupg = GnuPGInterface.GnuPG()
144
144
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
145
 
        self.gnupgargs = ['--batch',
146
 
                          '--home', self.tempdir,
147
 
                          '--force-mdc',
148
 
                          '--quiet',
149
 
                          '--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'])
150
151
    
151
152
    def __enter__(self):
152
153
        return self
174
175
    def password_encode(self, password):
175
176
        # Passphrase can not be empty and can not contain newlines or
176
177
        # NUL bytes.  So we prefix it and hex encode it.
177
 
        encoded = b"mandos" + binascii.hexlify(password)
178
 
        if len(encoded) > 2048:
179
 
            # GnuPG can't handle long passwords, so encode differently
180
 
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
181
 
                       .replace(b"\n", b"\\n")
182
 
                       .replace(b"\0", b"\\x00"))
183
 
        return encoded
 
178
        return b"mandos" + binascii.hexlify(password)
184
179
    
185
180
    def encrypt(self, data, password):
186
 
        passphrase = self.password_encode(password)
187
 
        with tempfile.NamedTemporaryFile(dir=self.tempdir
188
 
                                         ) as passfile:
189
 
            passfile.write(passphrase)
190
 
            passfile.flush()
191
 
            proc = subprocess.Popen(['gpg', '--symmetric',
192
 
                                     '--passphrase-file',
193
 
                                     passfile.name]
194
 
                                    + self.gnupgargs,
195
 
                                    stdin = subprocess.PIPE,
196
 
                                    stdout = subprocess.PIPE,
197
 
                                    stderr = subprocess.PIPE)
198
 
            ciphertext, err = proc.communicate(input = data)
199
 
        if proc.returncode != 0:
200
 
            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
201
195
        return ciphertext
202
196
    
203
197
    def decrypt(self, data, password):
204
 
        passphrase = self.password_encode(password)
205
 
        with tempfile.NamedTemporaryFile(dir = self.tempdir
206
 
                                         ) as passfile:
207
 
            passfile.write(passphrase)
208
 
            passfile.flush()
209
 
            proc = subprocess.Popen(['gpg', '--decrypt',
210
 
                                     '--passphrase-file',
211
 
                                     passfile.name]
212
 
                                    + self.gnupgargs,
213
 
                                    stdin = subprocess.PIPE,
214
 
                                    stdout = subprocess.PIPE,
215
 
                                    stderr = subprocess.PIPE)
216
 
            decrypted_plaintext, err = proc.communicate(input
217
 
                                                        = data)
218
 
        if proc.returncode != 0:
219
 
            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
220
212
        return decrypted_plaintext
221
213
 
222
214
 
448
440
    runtime_expansions: Allowed attributes for runtime expansion.
449
441
    expires:    datetime.datetime(); time (UTC) when a client will be
450
442
                disabled, or None
451
 
    server_settings: The server_settings dict from main()
452
443
    """
453
444
    
454
445
    runtime_expansions = ("approval_delay", "approval_duration",
529
520
        
530
521
        return settings
531
522
    
532
 
    def __init__(self, settings, name = None, server_settings=None):
 
523
    def __init__(self, settings, name = None):
533
524
        self.name = name
534
 
        if server_settings is None:
535
 
            server_settings = {}
536
 
        self.server_settings = server_settings
537
525
        # adding all client settings
538
526
        for setting, value in settings.iteritems():
539
527
            setattr(self, setting, value)
692
680
        # If a checker exists, make sure it is not a zombie
693
681
        try:
694
682
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
695
 
        except AttributeError:
696
 
            pass
697
 
        except OSError as error:
698
 
            if error.errno != errno.ECHILD:
699
 
                raise
 
683
        except (AttributeError, OSError) as error:
 
684
            if (isinstance(error, OSError)
 
685
                and error.errno != errno.ECHILD):
 
686
                raise error
700
687
        else:
701
688
            if pid:
702
689
                logger.warning("Checker was a zombie")
724
711
                # in normal mode, that is already done by daemon(),
725
712
                # and in debug mode we don't want to.  (Stdin is
726
713
                # always replaced by /dev/null.)
727
 
                # The exception is when not debugging but nevertheless
728
 
                # running in the foreground; use the previously
729
 
                # created wnull.
730
 
                popen_args = {}
731
 
                if (not self.server_settings["debug"]
732
 
                    and self.server_settings["foreground"]):
733
 
                    popen_args.update({"stdout": wnull,
734
 
                                       "stderr": wnull })
735
714
                self.checker = subprocess.Popen(command,
736
715
                                                close_fds=True,
737
 
                                                shell=True, cwd="/",
738
 
                                                **popen_args)
 
716
                                                shell=True, cwd="/")
739
717
            except OSError as error:
740
718
                logger.error("Failed to start subprocess",
741
719
                             exc_info=error)
936
914
            # The byte_arrays option is not supported yet on
937
915
            # signatures other than "ay".
938
916
            if prop._dbus_signature != "ay":
939
 
                raise ValueError("Byte arrays not supported for non-"
940
 
                                 "'ay' signature {0!r}"
941
 
                                 .format(prop._dbus_signature))
 
917
                raise ValueError
942
918
            value = dbus.ByteArray(b''.join(chr(byte)
943
919
                                            for byte in value))
944
920
        prop(value)
1352
1328
                                       *args, **kwargs)
1353
1329
    
1354
1330
    def start_checker(self, *args, **kwargs):
1355
 
        old_checker_pid = getattr(self.checker, "pid", None)
 
1331
        old_checker = self.checker
 
1332
        if self.checker is not None:
 
1333
            old_checker_pid = self.checker.pid
 
1334
        else:
 
1335
            old_checker_pid = None
1356
1336
        r = Client.start_checker(self, *args, **kwargs)
1357
1337
        # Only if new checker process was started
1358
1338
        if (self.checker is not None
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
    
2342
2321
    if options.check:
2343
2322
        import doctest
2344
 
        fail_count, test_count = doctest.testmod()
2345
 
        sys.exit(os.EX_OK if fail_count == 0 else 1)
 
2323
        doctest.testmod()
 
2324
        sys.exit()
2346
2325
    
2347
2326
    # Default values for config file for server-global settings
2348
2327
    server_defaults = { "interface": "",
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: