/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: 2015-07-20 03:03:33 UTC
  • Revision ID: teddy@recompile.se-20150720030333-203m2aeblypcsfte
Bug fix for GnuTLS 3: be compatible with old 2048-bit DSA keys.

The mandos-keygen program in Mandos version 1.6.0 and older generated
2048-bit DSA keys, and when GnuTLS uses these it has trouble
connecting using the Mandos default priority string.  This was
previously fixed in Mandos 1.6.2, but the bug reappeared when using
GnuTLS 3, so the default priority string has to change again; this
time also the Mandos client has to change its default, so now the
server and the client should use the same default priority string:

SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256

* mandos (main/server_defaults): Changed default priority string.
* mandos-options.xml (/section/para[id="priority_compat"]): Removed.
  (/section/para[id="priority"]): Changed default priority string.
* mandos.conf ([DEFAULT]/priority): - '' -
* mandos.conf.xml (OPTIONS/priority): Refer to the id "priority"
                                      instead of "priority_compat".
* mandos.xml (OPTIONS/--priority): - '' -
* plugins.d/mandos-client.c (main): Changed default priority string.

Show diffs side-by-side

added added

removed removed

Lines of Context:
395
395
                    logger.error(bad_states[state] + ": %r", error)
396
396
            self.cleanup()
397
397
        elif state == avahi.SERVER_RUNNING:
398
 
            self.add()
 
398
            try:
 
399
                self.add()
 
400
            except dbus.exceptions.DBusException as error:
 
401
                if (error.get_dbus_name()
 
402
                    == "org.freedesktop.Avahi.CollisionError"):
 
403
                    logger.info("Local Zeroconf service name"
 
404
                                " collision.")
 
405
                    return self.rename(remove=False)
 
406
                else:
 
407
                    logger.critical("D-Bus Exception", exc_info=error)
 
408
                    self.cleanup()
 
409
                    os._exit(1)
399
410
        else:
400
411
            if error is None:
401
412
                logger.debug("Unknown state: %r", state)
424
435
            .format(self.name)))
425
436
        return ret
426
437
 
 
438
def call_pipe(connection,       # : multiprocessing.Connection
 
439
              func, *args, **kwargs):
 
440
    """This function is meant to be called by multiprocessing.Process
 
441
    
 
442
    This function runs func(*args, **kwargs), and writes the resulting
 
443
    return value on the provided multiprocessing.Connection.
 
444
    """
 
445
    connection.send(func(*args, **kwargs))
 
446
    connection.close()
427
447
 
428
448
class Client(object):
429
449
    """A representation of a client host served by this server.
456
476
    last_checker_status: integer between 0 and 255 reflecting exit
457
477
                         status of last checker. -1 reflects crashed
458
478
                         checker, -2 means no checker completed yet.
 
479
    last_checker_signal: The signal which killed the last checker, if
 
480
                         last_checker_status is -1
459
481
    last_enabled: datetime.datetime(); (UTC) or None
460
482
    name:       string; from the config file, used in log messages and
461
483
                        D-Bus identifiers
635
657
        # Also start a new checker *right now*.
636
658
        self.start_checker()
637
659
    
638
 
    def checker_callback(self, pid, condition, command):
 
660
    def checker_callback(self, source, condition, connection,
 
661
                         command):
639
662
        """The checker has completed, so take appropriate actions."""
640
663
        self.checker_callback_tag = None
641
664
        self.checker = None
642
 
        if os.WIFEXITED(condition):
643
 
            self.last_checker_status = os.WEXITSTATUS(condition)
 
665
        # Read return code from connection (see call_pipe)
 
666
        returncode = connection.recv()
 
667
        connection.close()
 
668
        
 
669
        if returncode >= 0:
 
670
            self.last_checker_status = returncode
 
671
            self.last_checker_signal = None
644
672
            if self.last_checker_status == 0:
645
673
                logger.info("Checker for %(name)s succeeded",
646
674
                            vars(self))
649
677
                logger.info("Checker for %(name)s failed", vars(self))
650
678
        else:
651
679
            self.last_checker_status = -1
 
680
            self.last_checker_signal = -returncode
652
681
            logger.warning("Checker for %(name)s crashed?",
653
682
                           vars(self))
 
683
        return False
654
684
    
655
685
    def checked_ok(self):
656
686
        """Assert that the client has been seen, alive and well."""
657
687
        self.last_checked_ok = datetime.datetime.utcnow()
658
688
        self.last_checker_status = 0
 
689
        self.last_checker_signal = None
659
690
        self.bump_timeout()
660
691
    
661
692
    def bump_timeout(self, timeout=None):
687
718
        # than 'timeout' for the client to be disabled, which is as it
688
719
        # should be.
689
720
        
690
 
        # If a checker exists, make sure it is not a zombie
691
 
        try:
692
 
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
693
 
        except AttributeError:
694
 
            pass
695
 
        except OSError as error:
696
 
            if error.errno != errno.ECHILD:
697
 
                raise
698
 
        else:
699
 
            if pid:
700
 
                logger.warning("Checker was a zombie")
701
 
                gobject.source_remove(self.checker_callback_tag)
702
 
                self.checker_callback(pid, status,
703
 
                                      self.current_checker_command)
 
721
        if self.checker is not None and not self.checker.is_alive():
 
722
            logger.warning("Checker was not alive; joining")
 
723
            self.checker.join()
 
724
            self.checker = None
704
725
        # Start a new checker if needed
705
726
        if self.checker is None:
706
727
            # Escape attributes for the shell
715
736
                             exc_info=error)
716
737
                return True     # Try again later
717
738
            self.current_checker_command = command
718
 
            try:
719
 
                logger.info("Starting checker %r for %s", command,
720
 
                            self.name)
721
 
                # We don't need to redirect stdout and stderr, since
722
 
                # in normal mode, that is already done by daemon(),
723
 
                # and in debug mode we don't want to.  (Stdin is
724
 
                # always replaced by /dev/null.)
725
 
                # The exception is when not debugging but nevertheless
726
 
                # running in the foreground; use the previously
727
 
                # created wnull.
728
 
                popen_args = {}
729
 
                if (not self.server_settings["debug"]
730
 
                    and self.server_settings["foreground"]):
731
 
                    popen_args.update({"stdout": wnull,
732
 
                                       "stderr": wnull })
733
 
                self.checker = subprocess.Popen(command,
734
 
                                                close_fds=True,
735
 
                                                shell=True,
736
 
                                                cwd="/",
737
 
                                                **popen_args)
738
 
            except OSError as error:
739
 
                logger.error("Failed to start subprocess",
740
 
                             exc_info=error)
741
 
                return True
742
 
            self.checker_callback_tag = gobject.child_watch_add(
743
 
                self.checker.pid, self.checker_callback, data=command)
744
 
            # The checker may have completed before the gobject
745
 
            # 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
755
 
            if pid:
756
 
                gobject.source_remove(self.checker_callback_tag)
757
 
                self.checker_callback(pid, status, command)
 
739
            logger.info("Starting checker %r for %s", command,
 
740
                        self.name)
 
741
            # We don't need to redirect stdout and stderr, since
 
742
            # in normal mode, that is already done by daemon(),
 
743
            # and in debug mode we don't want to.  (Stdin is
 
744
            # always replaced by /dev/null.)
 
745
            # The exception is when not debugging but nevertheless
 
746
            # running in the foreground; use the previously
 
747
            # created wnull.
 
748
            popen_args = { "close_fds": True,
 
749
                           "shell": True,
 
750
                           "cwd": "/" }
 
751
            if (not self.server_settings["debug"]
 
752
                and self.server_settings["foreground"]):
 
753
                popen_args.update({"stdout": wnull,
 
754
                                   "stderr": wnull })
 
755
            pipe = multiprocessing.Pipe(duplex = False)
 
756
            self.checker = multiprocessing.Process(
 
757
                target = call_pipe,
 
758
                args = (pipe[1], subprocess.call, command),
 
759
                kwargs = popen_args)
 
760
            self.checker.start()
 
761
            self.checker_callback_tag = gobject.io_add_watch(
 
762
                pipe[0].fileno(), gobject.IO_IN,
 
763
                self.checker_callback, pipe[0], command)
758
764
        # Re-run this periodically if run by gobject.timeout_add
759
765
        return True
760
766
    
766
772
        if getattr(self, "checker", None) is None:
767
773
            return
768
774
        logger.debug("Stopping checker for %(name)s", vars(self))
769
 
        try:
770
 
            self.checker.terminate()
771
 
            #time.sleep(0.5)
772
 
            #if self.checker.poll() is None:
773
 
            #    self.checker.kill()
774
 
        except OSError as error:
775
 
            if error.errno != errno.ESRCH: # No such process
776
 
                raise
 
775
        self.checker.terminate()
777
776
        self.checker = None
778
777
 
779
778
 
1111
1110
                interface_names.add(alt_interface)
1112
1111
                # Is this a D-Bus signal?
1113
1112
                if getattr(attribute, "_dbus_is_signal", False):
1114
 
                    # Extract the original non-method undecorated
1115
 
                    # function by black magic
1116
 
                    nonmethod_func = (dict(
1117
 
                        zip(attribute.func_code.co_freevars,
1118
 
                            attribute.__closure__))
1119
 
                                      ["func"].cell_contents)
 
1113
                    if sys.version_info.major == 2:
 
1114
                        # Extract the original non-method undecorated
 
1115
                        # function by black magic
 
1116
                        nonmethod_func = (dict(
 
1117
                            zip(attribute.func_code.co_freevars,
 
1118
                                attribute.__closure__))
 
1119
                                          ["func"].cell_contents)
 
1120
                    else:
 
1121
                        nonmethod_func = attribute
1120
1122
                    # Create a new, but exactly alike, function
1121
1123
                    # object, and decorate it to be a new D-Bus signal
1122
1124
                    # with the alternate D-Bus interface name
 
1125
                    if sys.version_info.major == 2:
 
1126
                        new_function = types.FunctionType(
 
1127
                            nonmethod_func.func_code,
 
1128
                            nonmethod_func.func_globals,
 
1129
                            nonmethod_func.func_name,
 
1130
                            nonmethod_func.func_defaults,
 
1131
                            nonmethod_func.func_closure)
 
1132
                    else:
 
1133
                        new_function = types.FunctionType(
 
1134
                            nonmethod_func.__code__,
 
1135
                            nonmethod_func.__globals__,
 
1136
                            nonmethod_func.__name__,
 
1137
                            nonmethod_func.__defaults__,
 
1138
                            nonmethod_func.__closure__)
1123
1139
                    new_function = (dbus.service.signal(
1124
 
                        alt_interface, attribute._dbus_signature)
1125
 
                                    (types.FunctionType(
1126
 
                                        nonmethod_func.func_code,
1127
 
                                        nonmethod_func.func_globals,
1128
 
                                        nonmethod_func.func_name,
1129
 
                                        nonmethod_func.func_defaults,
1130
 
                                        nonmethod_func.func_closure)))
 
1140
                        alt_interface,
 
1141
                        attribute._dbus_signature)(new_function))
1131
1142
                    # Copy annotations, if any
1132
1143
                    try:
1133
1144
                        new_function._dbus_annotations = dict(
1356
1367
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
1357
1368
        Client.__del__(self, *args, **kwargs)
1358
1369
    
1359
 
    def checker_callback(self, pid, condition, command,
1360
 
                         *args, **kwargs):
1361
 
        self.checker_callback_tag = None
1362
 
        self.checker = None
1363
 
        if os.WIFEXITED(condition):
1364
 
            exitstatus = os.WEXITSTATUS(condition)
 
1370
    def checker_callback(self, source, condition,
 
1371
                         connection, command, *args, **kwargs):
 
1372
        ret = Client.checker_callback(self, source, condition,
 
1373
                                      connection, command, *args,
 
1374
                                      **kwargs)
 
1375
        exitstatus = self.last_checker_status
 
1376
        if exitstatus >= 0:
1365
1377
            # Emit D-Bus signal
1366
1378
            self.CheckerCompleted(dbus.Int16(exitstatus),
1367
 
                                  dbus.Int64(condition),
 
1379
                                  dbus.Int64(0),
1368
1380
                                  dbus.String(command))
1369
1381
        else:
1370
1382
            # Emit D-Bus signal
1371
1383
            self.CheckerCompleted(dbus.Int16(-1),
1372
 
                                  dbus.Int64(condition),
 
1384
                                  dbus.Int64(
 
1385
                                      self.last_checker_signal),
1373
1386
                                  dbus.String(command))
1374
 
        
1375
 
        return Client.checker_callback(self, pid, condition, command,
1376
 
                                       *args, **kwargs)
 
1387
        return ret
1377
1388
    
1378
1389
    def start_checker(self, *args, **kwargs):
1379
1390
        old_checker_pid = getattr(self.checker, "pid", None)
2141
2152
        
2142
2153
        if command == 'getattr':
2143
2154
            attrname = request[1]
2144
 
            if callable(client_object.__getattribute__(attrname)):
 
2155
            if isinstance(client_object.__getattribute__(attrname),
 
2156
                          collections.Callable):
2145
2157
                parent_pipe.send(('function', ))
2146
2158
            else:
2147
2159
                parent_pipe.send((
2385
2397
                        "debug": "False",
2386
2398
                        "priority":
2387
2399
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2388
 
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2400
                        ":+SIGN-DSA-SHA256",
2389
2401
                        "servicename": "Mandos",
2390
2402
                        "use_dbus": "True",
2391
2403
                        "use_ipv6": "True",
2647
2659
                    pass
2648
2660
            
2649
2661
            # Clients who has passed its expire date can still be
2650
 
            # enabled if its last checker was successful.  Clients
 
2662
            # enabled if its last checker was successful.  A Client
2651
2663
            # whose checker succeeded before we stored its state is
2652
2664
            # assumed to have successfully run all checkers during
2653
2665
            # downtime.