/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-23 00:58:49 UTC
  • Revision ID: teddy@recompile.se-20120623005849-02wj82cng433rt2k
* clients.conf: Convert all time intervals to new RFC 3339 syntax.
* mandos: All client options for time intervals now take an RFC 3339
          duration.
  (rfc3339_duration_to_delta): New function.
  (string_to_delta): Try rfc3339_duration_to_delta first.
* mandos-clients.conf.xml (OPTIONS/timeout): Document new format.
  (EXAMPLE): Update to new interval format.
  (SEE ALSO): Reference RFC 3339.

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.1"
 
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)
739
 
                return True
740
720
            self.checker_callback_tag = (gobject.child_watch_add
741
721
                                         (self.checker.pid,
742
722
                                          self.checker_callback,
743
723
                                          data=command))
744
724
            # The checker may have completed before the gobject
745
725
            # watch was added.  Check for this.
746
 
            try:
747
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
748
 
            except OSError as error:
749
 
                if error.errno == errno.ECHILD:
750
 
                    # This should never happen
751
 
                    logger.error("Child process vanished",
752
 
                                 exc_info=error)
753
 
                    return True
754
 
                raise
 
726
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
755
727
            if pid:
756
728
                gobject.source_remove(self.checker_callback_tag)
757
729
                self.checker_callback(pid, status, command)
1097
1069
                interface_names.add(alt_interface)
1098
1070
                # Is this a D-Bus signal?
1099
1071
                if getattr(attribute, "_dbus_is_signal", False):
1100
 
                    # Extract the original non-method undecorated
1101
 
                    # function by black magic
 
1072
                    # Extract the original non-method function by
 
1073
                    # black magic
1102
1074
                    nonmethod_func = (dict(
1103
1075
                            zip(attribute.func_code.co_freevars,
1104
1076
                                attribute.__closure__))["func"]
1997
1969
                if self.address_family == socket.AF_INET6:
1998
1970
                    any_address = "::" # in6addr_any
1999
1971
                else:
2000
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
1972
                    any_address = socket.INADDR_ANY
2001
1973
                self.server_address = (any_address,
2002
1974
                                       self.server_address[1])
2003
1975
            elif not self.server_address[1]:
2308
2280
                        help="Run self-test")
2309
2281
    parser.add_argument("--debug", action="store_true",
2310
2282
                        help="Debug mode; run in foreground and log"
2311
 
                        " to terminal", default=None)
 
2283
                        " to terminal")
2312
2284
    parser.add_argument("--debuglevel", metavar="LEVEL",
2313
2285
                        help="Debug level for stdout output")
2314
2286
    parser.add_argument("--priority", help="GnuTLS"
2321
2293
                        " files")
2322
2294
    parser.add_argument("--no-dbus", action="store_false",
2323
2295
                        dest="use_dbus", help="Do not provide D-Bus"
2324
 
                        " system bus interface", default=None)
 
2296
                        " system bus interface")
2325
2297
    parser.add_argument("--no-ipv6", action="store_false",
2326
 
                        dest="use_ipv6", help="Do not use IPv6",
2327
 
                        default=None)
 
2298
                        dest="use_ipv6", help="Do not use IPv6")
2328
2299
    parser.add_argument("--no-restore", action="store_false",
2329
2300
                        dest="restore", help="Do not restore stored"
2330
 
                        " state", default=None)
 
2301
                        " state")
2331
2302
    parser.add_argument("--socket", type=int,
2332
2303
                        help="Specify a file descriptor to a network"
2333
2304
                        " socket to use instead of creating one")
2334
2305
    parser.add_argument("--statedir", metavar="DIR",
2335
2306
                        help="Directory to save/restore state in")
2336
2307
    parser.add_argument("--foreground", action="store_true",
2337
 
                        help="Run in foreground", default=None)
 
2308
                        help="Run in foreground")
2338
2309
    
2339
2310
    options = parser.parse_args()
2340
2311
    
2349
2320
                        "port": "",
2350
2321
                        "debug": "False",
2351
2322
                        "priority":
2352
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2323
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2353
2324
                        "servicename": "Mandos",
2354
2325
                        "use_dbus": "True",
2355
2326
                        "use_ipv6": "True",
2399
2370
    for option in server_settings.keys():
2400
2371
        if type(server_settings[option]) is str:
2401
2372
            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
2373
    # Debug implies foreground
2407
2374
    if server_settings["debug"]:
2408
2375
        server_settings["foreground"] = True
2456
2423
                              socketfd=(server_settings["socket"]
2457
2424
                                        or None))
2458
2425
    if not foreground:
2459
 
        pidfilename = "/run/mandos.pid"
 
2426
        pidfilename = "/var/run/mandos.pid"
2460
2427
        pidfile = None
2461
2428
        try:
2462
2429
            pidfile = open(pidfilename, "w")
2548
2515
    old_client_settings = {}
2549
2516
    clients_data = {}
2550
2517
    
2551
 
    # This is used to redirect stdout and stderr for checker processes
2552
 
    global wnull
2553
 
    wnull = open(os.devnull, "w") # A writable /dev/null
2554
 
    # Only used if server is running in foreground but not in debug
2555
 
    # mode
2556
 
    if debug or not foreground:
2557
 
        wnull.close()
2558
 
    
2559
2518
    # Get client data and settings from last running state.
2560
2519
    if server_settings["restore"]:
2561
2520
        try:
2577
2536
    
2578
2537
    with PGPEngine() as pgp:
2579
2538
        for client_name, client in clients_data.iteritems():
2580
 
            # Skip removed clients
2581
 
            if client_name not in client_settings:
2582
 
                continue
2583
 
            
2584
2539
            # Decide which value to use after restoring saved state.
2585
2540
            # We have three different values: Old config file,
2586
2541
            # new config file, and saved state.
2648
2603
    # Create all client objects
2649
2604
    for client_name, client in clients_data.iteritems():
2650
2605
        tcp_server.clients[client_name] = client_class(
2651
 
            name = client_name, settings = client,
2652
 
            server_settings = server_settings)
 
2606
            name = client_name, settings = client)
2653
2607
    
2654
2608
    if not tcp_server.clients:
2655
2609
        logger.warning("No clients defined")
2738
2692
        service.cleanup()
2739
2693
        
2740
2694
        multiprocessing.active_children()
2741
 
        wnull.close()
2742
2695
        if not (tcp_server.clients or client_settings):
2743
2696
            return
2744
2697
        
2756
2709
                # A list of attributes that can not be pickled
2757
2710
                # + secret.
2758
2711
                exclude = set(("bus", "changedstate", "secret",
2759
 
                               "checker", "server_settings"))
 
2712
                               "checker"))
2760
2713
                for name, typ in (inspect.getmembers
2761
2714
                                  (dbus.service.Object)):
2762
2715
                    exclude.add(name)