/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: 2013-10-20 15:25:09 UTC
  • mto: (237.7.272 trunk)
  • mto: This revision was merged to the branch mainline in revision 305.
  • Revision ID: teddy@recompile.se-20131020152509-zkhuy2yse76w10hg
* Makefile (CFLAGS, LDFLAGS): Keep default flags from environment.
  (purge-server): PID file changed to "/run/mandos.pid".
* debian/compat: Changed to "9".
* debian/control (Standards-Version): Updated to "3.9.4".
  (DM-Upload-Allowed): Removed.
  (mandos/Depends): Add "initscripts (>= 2.88dsf-13.3)" to be able to
                    use the "/run" directory (for mandos.pid).
* debian/copyright (Copyright): Update year.
* init.d-mandos (PIDFILE): Changed to "/run/mandos.pid".
* mandos: Update copyright year.
  (pidfilename): Changed to "/run/mandos.pid".
* mandos-clients.conf.xml (OPTIONS/approval_delay): Bug fix: default
                                                    is "PT0S" - using
                                                    the new RFC 3339
                                                    duration syntax.
* mandos-keygen: Update copyright year.
* mandos-monitor: - '' -
* mandos.conf.xml: - '' -
* mandos.xml: - '' -
  (FILES): PID file changed to "/run/mandos.pid".
* plugin-runner.c: Update copyright year.
* plugins.d/mandos-client.c: - '' -
* plugins.d/mandos-client.xml: - '' -
* plugins.d/password-prompt.c: - '' -
* plugins.d/plymouth.c: - '' -

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-2012 Teddy Hogeborn
15
 
# Copyright © 2008-2012 Björn Påhlsson
 
14
# Copyright © 2008-2013 Teddy Hogeborn
 
15
# Copyright © 2008-2013 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
83
82
 
84
83
try:
85
84
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
89
88
    except ImportError:
90
89
        SO_BINDTODEVICE = None
91
90
 
92
 
version = "1.6.0"
 
91
version = "1.6.1"
93
92
stored_state_file = "clients.pickle"
94
93
 
95
94
logger = logging.getLogger()
140
139
class PGPEngine(object):
141
140
    """A simple class for OpenPGP symmetric encryption & decryption"""
142
141
    def __init__(self):
143
 
        self.gnupg = GnuPGInterface.GnuPG()
144
142
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
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'])
 
143
        self.gnupgargs = ['--batch',
 
144
                          '--home', self.tempdir,
 
145
                          '--force-mdc',
 
146
                          '--quiet',
 
147
                          '--no-use-agent']
151
148
    
152
149
    def __enter__(self):
153
150
        return self
178
175
        return b"mandos" + binascii.hexlify(password)
179
176
    
180
177
    def encrypt(self, data, password):
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
 
178
        passphrase = self.password_encode(password)
 
179
        with tempfile.NamedTemporaryFile(dir=self.tempdir
 
180
                                         ) as passfile:
 
181
            passfile.write(passphrase)
 
182
            passfile.flush()
 
183
            proc = subprocess.Popen(['gpg', '--symmetric',
 
184
                                     '--passphrase-file',
 
185
                                     passfile.name]
 
186
                                    + self.gnupgargs,
 
187
                                    stdin = subprocess.PIPE,
 
188
                                    stdout = subprocess.PIPE,
 
189
                                    stderr = subprocess.PIPE)
 
190
            ciphertext, err = proc.communicate(input = data)
 
191
        if proc.returncode != 0:
 
192
            raise PGPError(err)
195
193
        return ciphertext
196
194
    
197
195
    def decrypt(self, data, password):
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
 
196
        passphrase = self.password_encode(password)
 
197
        with tempfile.NamedTemporaryFile(dir = self.tempdir
 
198
                                         ) as passfile:
 
199
            passfile.write(passphrase)
 
200
            passfile.flush()
 
201
            proc = subprocess.Popen(['gpg', '--decrypt',
 
202
                                     '--passphrase-file',
 
203
                                     passfile.name]
 
204
                                    + self.gnupgargs,
 
205
                                    stdin = subprocess.PIPE,
 
206
                                    stdout = subprocess.PIPE,
 
207
                                    stderr = subprocess.PIPE)
 
208
            decrypted_plaintext, err = proc.communicate(input
 
209
                                                        = data)
 
210
        if proc.returncode != 0:
 
211
            raise PGPError(err)
212
212
        return decrypted_plaintext
213
213
 
214
214
 
234
234
               Used to optionally bind to the specified interface.
235
235
    name: string; Example: 'Mandos'
236
236
    type: string; Example: '_mandos._tcp'.
237
 
                  See <http://www.dns-sd.org/ServiceTypes.html>
 
237
     See <https://www.iana.org/assignments/service-names-port-numbers>
238
238
    port: integer; what port to announce
239
239
    TXT: list of strings; TXT record for the service
240
240
    domain: string; Domain to publish on, default to .local if empty.
440
440
    runtime_expansions: Allowed attributes for runtime expansion.
441
441
    expires:    datetime.datetime(); time (UTC) when a client will be
442
442
                disabled, or None
 
443
    server_settings: The server_settings dict from main()
443
444
    """
444
445
    
445
446
    runtime_expansions = ("approval_delay", "approval_duration",
520
521
        
521
522
        return settings
522
523
    
523
 
    def __init__(self, settings, name = None):
 
524
    def __init__(self, settings, name = None, server_settings=None):
524
525
        self.name = name
 
526
        if server_settings is None:
 
527
            server_settings = {}
 
528
        self.server_settings = server_settings
525
529
        # adding all client settings
526
530
        for setting, value in settings.iteritems():
527
531
            setattr(self, setting, value)
711
715
                # in normal mode, that is already done by daemon(),
712
716
                # and in debug mode we don't want to.  (Stdin is
713
717
                # always replaced by /dev/null.)
 
718
                # The exception is when not debugging but nevertheless
 
719
                # running in the foreground; use the previously
 
720
                # created wnull.
 
721
                popen_args = {}
 
722
                if (not self.server_settings["debug"]
 
723
                    and self.server_settings["foreground"]):
 
724
                    popen_args.update({"stdout": wnull,
 
725
                                       "stderr": wnull })
714
726
                self.checker = subprocess.Popen(command,
715
727
                                                close_fds=True,
716
 
                                                shell=True, cwd="/")
 
728
                                                shell=True, cwd="/",
 
729
                                                **popen_args)
717
730
            except OSError as error:
718
731
                logger.error("Failed to start subprocess",
719
732
                             exc_info=error)
728
741
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
729
742
            except OSError as error:
730
743
                if error.errno == errno.ECHILD:
731
 
                    logger.error("Child process vanished", exc_info=error)
 
744
                    # This should never happen
 
745
                    logger.error("Child process vanished",
 
746
                                 exc_info=error)
732
747
                    return True
733
748
                raise
734
749
            if pid:
1076
1091
                interface_names.add(alt_interface)
1077
1092
                # Is this a D-Bus signal?
1078
1093
                if getattr(attribute, "_dbus_is_signal", False):
1079
 
                    # Extract the original non-method function by
1080
 
                    # black magic
 
1094
                    # Extract the original non-method undecorated
 
1095
                    # function by black magic
1081
1096
                    nonmethod_func = (dict(
1082
1097
                            zip(attribute.func_code.co_freevars,
1083
1098
                                attribute.__closure__))["func"]
1976
1991
                if self.address_family == socket.AF_INET6:
1977
1992
                    any_address = "::" # in6addr_any
1978
1993
                else:
1979
 
                    any_address = socket.INADDR_ANY
 
1994
                    any_address = "0.0.0.0" # INADDR_ANY
1980
1995
                self.server_address = (any_address,
1981
1996
                                       self.server_address[1])
1982
1997
            elif not self.server_address[1]:
2287
2302
                        help="Run self-test")
2288
2303
    parser.add_argument("--debug", action="store_true",
2289
2304
                        help="Debug mode; run in foreground and log"
2290
 
                        " to terminal")
 
2305
                        " to terminal", default=None)
2291
2306
    parser.add_argument("--debuglevel", metavar="LEVEL",
2292
2307
                        help="Debug level for stdout output")
2293
2308
    parser.add_argument("--priority", help="GnuTLS"
2300
2315
                        " files")
2301
2316
    parser.add_argument("--no-dbus", action="store_false",
2302
2317
                        dest="use_dbus", help="Do not provide D-Bus"
2303
 
                        " system bus interface")
 
2318
                        " system bus interface", default=None)
2304
2319
    parser.add_argument("--no-ipv6", action="store_false",
2305
 
                        dest="use_ipv6", help="Do not use IPv6")
 
2320
                        dest="use_ipv6", help="Do not use IPv6",
 
2321
                        default=None)
2306
2322
    parser.add_argument("--no-restore", action="store_false",
2307
2323
                        dest="restore", help="Do not restore stored"
2308
 
                        " state")
 
2324
                        " state", default=None)
2309
2325
    parser.add_argument("--socket", type=int,
2310
2326
                        help="Specify a file descriptor to a network"
2311
2327
                        " socket to use instead of creating one")
2312
2328
    parser.add_argument("--statedir", metavar="DIR",
2313
2329
                        help="Directory to save/restore state in")
2314
2330
    parser.add_argument("--foreground", action="store_true",
2315
 
                        help="Run in foreground")
 
2331
                        help="Run in foreground", default=None)
2316
2332
    
2317
2333
    options = parser.parse_args()
2318
2334
    
2327
2343
                        "port": "",
2328
2344
                        "debug": "False",
2329
2345
                        "priority":
2330
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
2346
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224",
2331
2347
                        "servicename": "Mandos",
2332
2348
                        "use_dbus": "True",
2333
2349
                        "use_ipv6": "True",
2377
2393
    for option in server_settings.keys():
2378
2394
        if type(server_settings[option]) is str:
2379
2395
            server_settings[option] = unicode(server_settings[option])
 
2396
    # Force all boolean options to be boolean
 
2397
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
 
2398
                   "foreground"):
 
2399
        server_settings[option] = bool(server_settings[option])
2380
2400
    # Debug implies foreground
2381
2401
    if server_settings["debug"]:
2382
2402
        server_settings["foreground"] = True
2430
2450
                              socketfd=(server_settings["socket"]
2431
2451
                                        or None))
2432
2452
    if not foreground:
2433
 
        pidfilename = "/var/run/mandos.pid"
 
2453
        pidfilename = "/run/mandos.pid"
2434
2454
        pidfile = None
2435
2455
        try:
2436
2456
            pidfile = open(pidfilename, "w")
2522
2542
    old_client_settings = {}
2523
2543
    clients_data = {}
2524
2544
    
 
2545
    # This is used to redirect stdout and stderr for checker processes
 
2546
    global wnull
 
2547
    wnull = open(os.devnull, "w") # A writable /dev/null
 
2548
    # Only used if server is running in foreground but not in debug
 
2549
    # mode
 
2550
    if debug or not foreground:
 
2551
        wnull.close()
 
2552
    
2525
2553
    # Get client data and settings from last running state.
2526
2554
    if server_settings["restore"]:
2527
2555
        try:
2543
2571
    
2544
2572
    with PGPEngine() as pgp:
2545
2573
        for client_name, client in clients_data.iteritems():
 
2574
            # Skip removed clients
 
2575
            if client_name not in client_settings:
 
2576
                continue
 
2577
            
2546
2578
            # Decide which value to use after restoring saved state.
2547
2579
            # We have three different values: Old config file,
2548
2580
            # new config file, and saved state.
2610
2642
    # Create all client objects
2611
2643
    for client_name, client in clients_data.iteritems():
2612
2644
        tcp_server.clients[client_name] = client_class(
2613
 
            name = client_name, settings = client)
 
2645
            name = client_name, settings = client,
 
2646
            server_settings = server_settings)
2614
2647
    
2615
2648
    if not tcp_server.clients:
2616
2649
        logger.warning("No clients defined")
2699
2732
        service.cleanup()
2700
2733
        
2701
2734
        multiprocessing.active_children()
 
2735
        wnull.close()
2702
2736
        if not (tcp_server.clients or client_settings):
2703
2737
            return
2704
2738
        
2716
2750
                # A list of attributes that can not be pickled
2717
2751
                # + secret.
2718
2752
                exclude = set(("bus", "changedstate", "secret",
2719
 
                               "checker"))
 
2753
                               "checker", "server_settings"))
2720
2754
                for name, typ in (inspect.getmembers
2721
2755
                                  (dbus.service.Object)):
2722
2756
                    exclude.add(name)