/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: 2012-06-23 13:50:05 UTC
  • mto: (237.7.272 trunk)
  • mto: This revision was merged to the branch mainline in revision 303.
  • Revision ID: teddy@recompile.se-20120623135005-t0wtkmlw0ymnr2kg
* mandos (Client.start_checker): Return True (retry later) if starting
                                 checker failed.  Check for ECHILD
                                 from waitpid().

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)
721
711
                # in normal mode, that is already done by daemon(),
722
712
                # and in debug mode we don't want to.  (Stdin is
723
713
                # always replaced by /dev/null.)
724
 
                # The exception is when not debugging but nevertheless
725
 
                # running in the foreground; use the previously
726
 
                # created wnull.
727
 
                popen_args = {}
728
 
                if (not self.server_settings["debug"]
729
 
                    and self.server_settings["foreground"]):
730
 
                    popen_args.update({"stdout": wnull,
731
 
                                       "stderr": wnull })
732
714
                self.checker = subprocess.Popen(command,
733
715
                                                close_fds=True,
734
 
                                                shell=True, cwd="/",
735
 
                                                **popen_args)
 
716
                                                shell=True, cwd="/")
736
717
            except OSError as error:
737
718
                logger.error("Failed to start subprocess",
738
719
                             exc_info=error)
747
728
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
748
729
            except OSError as error:
749
730
                if error.errno == errno.ECHILD:
750
 
                    # This should never happen
751
 
                    logger.error("Child process vanished",
752
 
                                 exc_info=error)
 
731
                    logger.error("Child process vanished", exc_info=error)
753
732
                    return True
754
733
                raise
755
734
            if pid:
1097
1076
                interface_names.add(alt_interface)
1098
1077
                # Is this a D-Bus signal?
1099
1078
                if getattr(attribute, "_dbus_is_signal", False):
1100
 
                    # Extract the original non-method undecorated
1101
 
                    # function by black magic
 
1079
                    # Extract the original non-method function by
 
1080
                    # black magic
1102
1081
                    nonmethod_func = (dict(
1103
1082
                            zip(attribute.func_code.co_freevars,
1104
1083
                                attribute.__closure__))["func"]
1997
1976
                if self.address_family == socket.AF_INET6:
1998
1977
                    any_address = "::" # in6addr_any
1999
1978
                else:
2000
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
1979
                    any_address = socket.INADDR_ANY
2001
1980
                self.server_address = (any_address,
2002
1981
                                       self.server_address[1])
2003
1982
            elif not self.server_address[1]:
2308
2287
                        help="Run self-test")
2309
2288
    parser.add_argument("--debug", action="store_true",
2310
2289
                        help="Debug mode; run in foreground and log"
2311
 
                        " to terminal", default=None)
 
2290
                        " to terminal")
2312
2291
    parser.add_argument("--debuglevel", metavar="LEVEL",
2313
2292
                        help="Debug level for stdout output")
2314
2293
    parser.add_argument("--priority", help="GnuTLS"
2321
2300
                        " files")
2322
2301
    parser.add_argument("--no-dbus", action="store_false",
2323
2302
                        dest="use_dbus", help="Do not provide D-Bus"
2324
 
                        " system bus interface", default=None)
 
2303
                        " system bus interface")
2325
2304
    parser.add_argument("--no-ipv6", action="store_false",
2326
 
                        dest="use_ipv6", help="Do not use IPv6",
2327
 
                        default=None)
 
2305
                        dest="use_ipv6", help="Do not use IPv6")
2328
2306
    parser.add_argument("--no-restore", action="store_false",
2329
2307
                        dest="restore", help="Do not restore stored"
2330
 
                        " state", default=None)
 
2308
                        " state")
2331
2309
    parser.add_argument("--socket", type=int,
2332
2310
                        help="Specify a file descriptor to a network"
2333
2311
                        " socket to use instead of creating one")
2334
2312
    parser.add_argument("--statedir", metavar="DIR",
2335
2313
                        help="Directory to save/restore state in")
2336
2314
    parser.add_argument("--foreground", action="store_true",
2337
 
                        help="Run in foreground", default=None)
 
2315
                        help="Run in foreground")
2338
2316
    
2339
2317
    options = parser.parse_args()
2340
2318
    
2349
2327
                        "port": "",
2350
2328
                        "debug": "False",
2351
2329
                        "priority":
2352
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2330
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2353
2331
                        "servicename": "Mandos",
2354
2332
                        "use_dbus": "True",
2355
2333
                        "use_ipv6": "True",
2399
2377
    for option in server_settings.keys():
2400
2378
        if type(server_settings[option]) is str:
2401
2379
            server_settings[option] = unicode(server_settings[option])
2402
 
    # Force all boolean options to be boolean
2403
 
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2404
 
                   "foreground"):
2405
 
        server_settings[option] = bool(server_settings[option])
2406
2380
    # Debug implies foreground
2407
2381
    if server_settings["debug"]:
2408
2382
        server_settings["foreground"] = True
2456
2430
                              socketfd=(server_settings["socket"]
2457
2431
                                        or None))
2458
2432
    if not foreground:
2459
 
        pidfilename = "/run/mandos.pid"
2460
 
        if not os.path.isdir("/run/."):
2461
 
            pidfilename = "/var/run/mandos.pid"
 
2433
        pidfilename = "/var/run/mandos.pid"
2462
2434
        pidfile = None
2463
2435
        try:
2464
2436
            pidfile = open(pidfilename, "w")
2550
2522
    old_client_settings = {}
2551
2523
    clients_data = {}
2552
2524
    
2553
 
    # This is used to redirect stdout and stderr for checker processes
2554
 
    global wnull
2555
 
    wnull = open(os.devnull, "w") # A writable /dev/null
2556
 
    # Only used if server is running in foreground but not in debug
2557
 
    # mode
2558
 
    if debug or not foreground:
2559
 
        wnull.close()
2560
 
    
2561
2525
    # Get client data and settings from last running state.
2562
2526
    if server_settings["restore"]:
2563
2527
        try:
2579
2543
    
2580
2544
    with PGPEngine() as pgp:
2581
2545
        for client_name, client in clients_data.iteritems():
2582
 
            # Skip removed clients
2583
 
            if client_name not in client_settings:
2584
 
                continue
2585
 
            
2586
2546
            # Decide which value to use after restoring saved state.
2587
2547
            # We have three different values: Old config file,
2588
2548
            # new config file, and saved state.
2650
2610
    # Create all client objects
2651
2611
    for client_name, client in clients_data.iteritems():
2652
2612
        tcp_server.clients[client_name] = client_class(
2653
 
            name = client_name, settings = client,
2654
 
            server_settings = server_settings)
 
2613
            name = client_name, settings = client)
2655
2614
    
2656
2615
    if not tcp_server.clients:
2657
2616
        logger.warning("No clients defined")
2740
2699
        service.cleanup()
2741
2700
        
2742
2701
        multiprocessing.active_children()
2743
 
        wnull.close()
2744
2702
        if not (tcp_server.clients or client_settings):
2745
2703
            return
2746
2704
        
2758
2716
                # A list of attributes that can not be pickled
2759
2717
                # + secret.
2760
2718
                exclude = set(("bus", "changedstate", "secret",
2761
 
                               "checker", "server_settings"))
 
2719
                               "checker"))
2762
2720
                for name, typ in (inspect.getmembers
2763
2721
                                  (dbus.service.Object)):
2764
2722
                    exclude.add(name)