/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:
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
175
178
        return b"mandos" + binascii.hexlify(password)
176
179
    
177
180
    def encrypt(self, data, password):
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)
 
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
193
195
        return ciphertext
194
196
    
195
197
    def decrypt(self, data, password):
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)
 
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
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 <https://www.iana.org/assignments/service-names-port-numbers>
 
237
                  See <http://www.dns-sd.org/ServiceTypes.html>
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()
444
443
    """
445
444
    
446
445
    runtime_expansions = ("approval_delay", "approval_duration",
521
520
        
522
521
        return settings
523
522
    
524
 
    def __init__(self, settings, name = None, server_settings=None):
 
523
    def __init__(self, settings, name = None):
525
524
        self.name = name
526
 
        if server_settings is None:
527
 
            server_settings = {}
528
 
        self.server_settings = server_settings
529
525
        # adding all client settings
530
526
        for setting, value in settings.iteritems():
531
527
            setattr(self, setting, value)
715
711
                # in normal mode, that is already done by daemon(),
716
712
                # and in debug mode we don't want to.  (Stdin is
717
713
                # 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 })
726
714
                self.checker = subprocess.Popen(command,
727
715
                                                close_fds=True,
728
 
                                                shell=True, cwd="/",
729
 
                                                **popen_args)
 
716
                                                shell=True, cwd="/")
730
717
            except OSError as error:
731
718
                logger.error("Failed to start subprocess",
732
719
                             exc_info=error)
733
 
                return True
734
720
            self.checker_callback_tag = (gobject.child_watch_add
735
721
                                         (self.checker.pid,
736
722
                                          self.checker_callback,
737
723
                                          data=command))
738
724
            # The checker may have completed before the gobject
739
725
            # watch was added.  Check for this.
740
 
            try:
741
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
742
 
            except OSError as error:
743
 
                if error.errno == errno.ECHILD:
744
 
                    # This should never happen
745
 
                    logger.error("Child process vanished",
746
 
                                 exc_info=error)
747
 
                    return True
748
 
                raise
 
726
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
749
727
            if pid:
750
728
                gobject.source_remove(self.checker_callback_tag)
751
729
                self.checker_callback(pid, status, command)
1091
1069
                interface_names.add(alt_interface)
1092
1070
                # Is this a D-Bus signal?
1093
1071
                if getattr(attribute, "_dbus_is_signal", False):
1094
 
                    # Extract the original non-method undecorated
1095
 
                    # function by black magic
 
1072
                    # Extract the original non-method function by
 
1073
                    # black magic
1096
1074
                    nonmethod_func = (dict(
1097
1075
                            zip(attribute.func_code.co_freevars,
1098
1076
                                attribute.__closure__))["func"]
1991
1969
                if self.address_family == socket.AF_INET6:
1992
1970
                    any_address = "::" # in6addr_any
1993
1971
                else:
1994
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
1972
                    any_address = socket.INADDR_ANY
1995
1973
                self.server_address = (any_address,
1996
1974
                                       self.server_address[1])
1997
1975
            elif not self.server_address[1]:
2302
2280
                        help="Run self-test")
2303
2281
    parser.add_argument("--debug", action="store_true",
2304
2282
                        help="Debug mode; run in foreground and log"
2305
 
                        " to terminal", default=None)
 
2283
                        " to terminal")
2306
2284
    parser.add_argument("--debuglevel", metavar="LEVEL",
2307
2285
                        help="Debug level for stdout output")
2308
2286
    parser.add_argument("--priority", help="GnuTLS"
2315
2293
                        " files")
2316
2294
    parser.add_argument("--no-dbus", action="store_false",
2317
2295
                        dest="use_dbus", help="Do not provide D-Bus"
2318
 
                        " system bus interface", default=None)
 
2296
                        " system bus interface")
2319
2297
    parser.add_argument("--no-ipv6", action="store_false",
2320
 
                        dest="use_ipv6", help="Do not use IPv6",
2321
 
                        default=None)
 
2298
                        dest="use_ipv6", help="Do not use IPv6")
2322
2299
    parser.add_argument("--no-restore", action="store_false",
2323
2300
                        dest="restore", help="Do not restore stored"
2324
 
                        " state", default=None)
 
2301
                        " state")
2325
2302
    parser.add_argument("--socket", type=int,
2326
2303
                        help="Specify a file descriptor to a network"
2327
2304
                        " socket to use instead of creating one")
2328
2305
    parser.add_argument("--statedir", metavar="DIR",
2329
2306
                        help="Directory to save/restore state in")
2330
2307
    parser.add_argument("--foreground", action="store_true",
2331
 
                        help="Run in foreground", default=None)
 
2308
                        help="Run in foreground")
2332
2309
    
2333
2310
    options = parser.parse_args()
2334
2311
    
2343
2320
                        "port": "",
2344
2321
                        "debug": "False",
2345
2322
                        "priority":
2346
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224",
 
2323
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2347
2324
                        "servicename": "Mandos",
2348
2325
                        "use_dbus": "True",
2349
2326
                        "use_ipv6": "True",
2393
2370
    for option in server_settings.keys():
2394
2371
        if type(server_settings[option]) is str:
2395
2372
            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])
2400
2373
    # Debug implies foreground
2401
2374
    if server_settings["debug"]:
2402
2375
        server_settings["foreground"] = True
2542
2515
    old_client_settings = {}
2543
2516
    clients_data = {}
2544
2517
    
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
 
    
2553
2518
    # Get client data and settings from last running state.
2554
2519
    if server_settings["restore"]:
2555
2520
        try:
2571
2536
    
2572
2537
    with PGPEngine() as pgp:
2573
2538
        for client_name, client in clients_data.iteritems():
2574
 
            # Skip removed clients
2575
 
            if client_name not in client_settings:
2576
 
                continue
2577
 
            
2578
2539
            # Decide which value to use after restoring saved state.
2579
2540
            # We have three different values: Old config file,
2580
2541
            # new config file, and saved state.
2642
2603
    # Create all client objects
2643
2604
    for client_name, client in clients_data.iteritems():
2644
2605
        tcp_server.clients[client_name] = client_class(
2645
 
            name = client_name, settings = client,
2646
 
            server_settings = server_settings)
 
2606
            name = client_name, settings = client)
2647
2607
    
2648
2608
    if not tcp_server.clients:
2649
2609
        logger.warning("No clients defined")
2732
2692
        service.cleanup()
2733
2693
        
2734
2694
        multiprocessing.active_children()
2735
 
        wnull.close()
2736
2695
        if not (tcp_server.clients or client_settings):
2737
2696
            return
2738
2697
        
2750
2709
                # A list of attributes that can not be pickled
2751
2710
                # + secret.
2752
2711
                exclude = set(("bus", "changedstate", "secret",
2753
 
                               "checker", "server_settings"))
 
2712
                               "checker"))
2754
2713
                for name, typ in (inspect.getmembers
2755
2714
                                  (dbus.service.Object)):
2756
2715
                    exclude.add(name)