/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: 2009-01-31 10:33:17 UTC
  • mfrom: (24.1.129 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20090131103317-wzqvyr532sjcjt7u
Merge from Björn:

* mandos-ctl: New option "--remove-client".  Only default to listing
              clients if no clients were given on the command line.
* plugins.d/mandos-client.c: Lower kernel log level while bringing up
                             network interface.  New option "--delay"
                             to control the maximum delay to wait for
                             running interface.
* plugins.d/mandos-client.xml (SYNOPSIS, OPTIONS): New option
                                                   "--delay".

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
 
36
36
import SocketServer
37
37
import socket
38
 
from optparse import OptionParser
 
38
import optparse
39
39
import datetime
40
40
import errno
41
41
import gnutls.crypto
66
66
import ctypes
67
67
import ctypes.util
68
68
 
69
 
version = "1.0.3"
 
69
version = "1.0.5"
70
70
 
71
71
logger = logging.Logger('mandos')
72
72
syslogger = (logging.handlers.SysLogHandler
178
178
class Client(dbus.service.Object):
179
179
    """A representation of a client host served by this server.
180
180
    Attributes:
181
 
    name:       string; from the config file, used in log messages
 
181
    name:       string; from the config file, used in log messages and
 
182
                        D-Bus identifiers
182
183
    fingerprint: string (40 or 32 hexadecimal digits); used to
183
184
                 uniquely identify the client
184
185
    secret:     bytestring; sent verbatim (over TLS) to client
225
226
        if config is None:
226
227
            config = {}
227
228
        logger.debug(u"Creating client %r", self.name)
228
 
        self.use_dbus = use_dbus
229
 
        if self.use_dbus:
230
 
            self.dbus_object_path = (dbus.ObjectPath
231
 
                                     ("/Mandos/clients/"
232
 
                                      + self.name.replace(".", "_")))
233
 
            dbus.service.Object.__init__(self, bus,
234
 
                                         self.dbus_object_path)
 
229
        self.use_dbus = False   # During __init__
235
230
        # Uppercase and remove spaces from fingerprint for later
236
231
        # comparison purposes with return value from the fingerprint()
237
232
        # function
261
256
        self.disable_initiator_tag = None
262
257
        self.checker_callback_tag = None
263
258
        self.checker_command = config["checker"]
 
259
        self.last_connect = None
 
260
        # Only now, when this client is initialized, can it show up on
 
261
        # the D-Bus
 
262
        self.use_dbus = use_dbus
 
263
        if self.use_dbus:
 
264
            self.dbus_object_path = (dbus.ObjectPath
 
265
                                     ("/clients/"
 
266
                                      + self.name.replace(".", "_")))
 
267
            dbus.service.Object.__init__(self, bus,
 
268
                                         self.dbus_object_path)
264
269
    
265
270
    def enable(self):
266
271
        """Start this client's checker and timeout hooks"""
319
324
            # Emit D-Bus signal
320
325
            self.PropertyChanged(dbus.String(u"checker_running"),
321
326
                                 dbus.Boolean(False, variant_level=1))
322
 
        if (os.WIFEXITED(condition)
323
 
            and (os.WEXITSTATUS(condition) == 0)):
324
 
            logger.info(u"Checker for %(name)s succeeded",
325
 
                        vars(self))
 
327
        if os.WIFEXITED(condition):
 
328
            exitstatus = os.WEXITSTATUS(condition)
 
329
            if exitstatus == 0:
 
330
                logger.info(u"Checker for %(name)s succeeded",
 
331
                            vars(self))
 
332
                self.checked_ok()
 
333
            else:
 
334
                logger.info(u"Checker for %(name)s failed",
 
335
                            vars(self))
326
336
            if self.use_dbus:
327
337
                # Emit D-Bus signal
328
 
                self.CheckerCompleted(dbus.Boolean(True),
329
 
                                      dbus.UInt16(condition),
 
338
                self.CheckerCompleted(dbus.Int16(exitstatus),
 
339
                                      dbus.Int64(condition),
330
340
                                      dbus.String(command))
331
 
            self.bump_timeout()
332
 
        elif not os.WIFEXITED(condition):
 
341
        else:
333
342
            logger.warning(u"Checker for %(name)s crashed?",
334
343
                           vars(self))
335
344
            if self.use_dbus:
336
345
                # Emit D-Bus signal
337
 
                self.CheckerCompleted(dbus.Boolean(False),
338
 
                                      dbus.UInt16(condition),
339
 
                                      dbus.String(command))
340
 
        else:
341
 
            logger.info(u"Checker for %(name)s failed",
342
 
                        vars(self))
343
 
            if self.use_dbus:
344
 
                # Emit D-Bus signal
345
 
                self.CheckerCompleted(dbus.Boolean(False),
346
 
                                      dbus.UInt16(condition),
 
346
                self.CheckerCompleted(dbus.Int16(-1),
 
347
                                      dbus.Int64(condition),
347
348
                                      dbus.String(command))
348
349
    
349
 
    def bump_timeout(self):
 
350
    def checked_ok(self):
350
351
        """Bump up the timeout for this client.
351
352
        This should only be called when the client has been seen,
352
353
        alive and well.
448
449
            return now < (self.last_checked_ok + self.timeout)
449
450
    
450
451
    ## D-Bus methods & signals
451
 
    _interface = u"org.mandos_system.Mandos.Client"
 
452
    _interface = u"se.bsnet.fukt.Mandos.Client"
452
453
    
453
 
    # BumpTimeout - method
454
 
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
455
 
    BumpTimeout.__name__ = "BumpTimeout"
 
454
    # CheckedOK - method
 
455
    CheckedOK = dbus.service.method(_interface)(checked_ok)
 
456
    CheckedOK.__name__ = "CheckedOK"
456
457
    
457
458
    # CheckerCompleted - signal
458
 
    @dbus.service.signal(_interface, signature="bqs")
459
 
    def CheckerCompleted(self, success, condition, command):
 
459
    @dbus.service.signal(_interface, signature="nxs")
 
460
    def CheckerCompleted(self, exitcode, waitstatus, command):
460
461
        "D-Bus signal"
461
462
        pass
462
463
    
503
504
                dbus.String("checker_running"):
504
505
                    dbus.Boolean(self.checker is not None,
505
506
                                 variant_level=1),
 
507
                dbus.String("object_path"):
 
508
                    dbus.ObjectPath(self.dbus_object_path,
 
509
                                    variant_level=1)
506
510
                }, signature="sv")
507
511
    
508
512
    # IsStillValid - method
591
595
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
592
596
        # ...do the normal thing
593
597
        return session.peer_certificate
594
 
    list_size = ctypes.c_uint()
 
598
    list_size = ctypes.c_uint(1)
595
599
    cert_list = (gnutls.library.functions
596
600
                 .gnutls_certificate_get_peers
597
601
                 (session._c_object, ctypes.byref(list_size)))
 
602
    if not bool(cert_list) and list_size.value != 0:
 
603
        raise gnutls.errors.GNUTLSError("error getting peer"
 
604
                                        " certificate")
598
605
    if list_size.value == 0:
599
606
        return None
600
607
    cert = cert_list[0]
684
691
            # Do not run session.bye() here: the session is not
685
692
            # established.  Just abandon the request.
686
693
            return
 
694
        logger.debug(u"Handshake succeeded")
687
695
        try:
688
696
            fpr = fingerprint(peer_certificate(session))
689
697
        except (TypeError, gnutls.errors.GNUTLSError), error:
691
699
            session.bye()
692
700
            return
693
701
        logger.debug(u"Fingerprint: %s", fpr)
 
702
        
694
703
        for c in self.server.clients:
695
704
            if c.fingerprint == fpr:
696
705
                client = c
709
718
            session.bye()
710
719
            return
711
720
        ## This won't work here, since we're in a fork.
712
 
        # client.bump_timeout()
 
721
        # client.checked_ok()
713
722
        sent_size = 0
714
723
        while sent_size < len(client.secret):
715
724
            sent = session.send(client.secret[sent_size:])
782
791
 
783
792
def string_to_delta(interval):
784
793
    """Parse a string and return a datetime.timedelta
785
 
 
 
794
    
786
795
    >>> string_to_delta('7d')
787
796
    datetime.timedelta(7)
788
797
    >>> string_to_delta('60s')
889
898
 
890
899
 
891
900
def main():
892
 
    parser = OptionParser(version = "%%prog %s" % version)
 
901
    parser = optparse.OptionParser(version = "%%prog %s" % version)
893
902
    parser.add_option("-i", "--interface", type="string",
894
903
                      metavar="IF", help="Bind to interface IF")
895
904
    parser.add_option("-a", "--address", type="string",
937
946
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
938
947
    # Convert the SafeConfigParser object to a dict
939
948
    server_settings = server_config.defaults()
940
 
    # Use getboolean on the boolean config options
941
 
    server_settings["debug"] = (server_config.getboolean
942
 
                                ("DEFAULT", "debug"))
943
 
    server_settings["use_dbus"] = (server_config.getboolean
944
 
                                   ("DEFAULT", "use_dbus"))
 
949
    # Use the appropriate methods on the non-string config options
 
950
    server_settings["debug"] = server_config.getboolean("DEFAULT",
 
951
                                                        "debug")
 
952
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
 
953
                                                           "use_dbus")
 
954
    if server_settings["port"]:
 
955
        server_settings["port"] = server_config.getint("DEFAULT",
 
956
                                                       "port")
945
957
    del server_config
946
958
    
947
959
    # Override the settings from the config file with command line
958
970
    # For convenience
959
971
    debug = server_settings["debug"]
960
972
    use_dbus = server_settings["use_dbus"]
 
973
 
 
974
    def sigsegvhandler(signum, frame):
 
975
        raise RuntimeError('Segmentation fault')
961
976
    
962
977
    if not debug:
963
978
        syslogger.setLevel(logging.WARNING)
964
979
        console.setLevel(logging.WARNING)
 
980
    else:
 
981
        signal.signal(signal.SIGSEGV, sigsegvhandler)
965
982
    
966
983
    if server_settings["servicename"] != "Mandos":
967
984
        syslogger.setFormatter(logging.Formatter
972
989
    # Parse config file with clients
973
990
    client_defaults = { "timeout": "1h",
974
991
                        "interval": "5m",
975
 
                        "checker": "fping -q -- %(host)s",
 
992
                        "checker": "fping -q -- %%(host)s",
976
993
                        "host": "",
977
994
                        }
978
995
    client_config = ConfigParser.SafeConfigParser(client_defaults)
993
1010
    
994
1011
    try:
995
1012
        uid = pwd.getpwnam("_mandos").pw_uid
 
1013
        gid = pwd.getpwnam("_mandos").pw_gid
996
1014
    except KeyError:
997
1015
        try:
998
1016
            uid = pwd.getpwnam("mandos").pw_uid
 
1017
            gid = pwd.getpwnam("mandos").pw_gid
999
1018
        except KeyError:
1000
1019
            try:
1001
1020
                uid = pwd.getpwnam("nobody").pw_uid
 
1021
                gid = pwd.getpwnam("nogroup").pw_gid
1002
1022
            except KeyError:
1003
1023
                uid = 65534
1004
 
    try:
1005
 
        gid = pwd.getpwnam("_mandos").pw_gid
1006
 
    except KeyError:
1007
 
        try:
1008
 
            gid = pwd.getpwnam("mandos").pw_gid
1009
 
        except KeyError:
1010
 
            try:
1011
 
                gid = pwd.getpwnam("nogroup").pw_gid
1012
 
            except KeyError:
1013
1024
                gid = 65534
1014
1025
    try:
 
1026
        os.setgid(gid)
1015
1027
        os.setuid(uid)
1016
 
        os.setgid(gid)
1017
1028
    except OSError, error:
1018
1029
        if error[0] != errno.EPERM:
1019
1030
            raise error
1020
1031
    
 
1032
    # Enable all possible GnuTLS debugging
 
1033
    if debug:
 
1034
        # "Use a log level over 10 to enable all debugging options."
 
1035
        # - GnuTLS manual
 
1036
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
1037
        
 
1038
        @gnutls.library.types.gnutls_log_func
 
1039
        def debug_gnutls(level, string):
 
1040
            logger.debug("GnuTLS: %s", string[:-1])
 
1041
        
 
1042
        (gnutls.library.functions
 
1043
         .gnutls_global_set_log_function(debug_gnutls))
 
1044
    
1021
1045
    global service
1022
1046
    service = AvahiService(name = server_settings["servicename"],
1023
1047
                           servicetype = "_mandos._tcp", )
1037
1061
                            avahi.DBUS_INTERFACE_SERVER)
1038
1062
    # End of Avahi example code
1039
1063
    if use_dbus:
1040
 
        bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
1041
 
                                        bus)
 
1064
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1042
1065
    
1043
1066
    clients.update(Set(Client(name = section,
1044
1067
                              config
1098
1121
        class MandosServer(dbus.service.Object):
1099
1122
            """A D-Bus proxy object"""
1100
1123
            def __init__(self):
1101
 
                dbus.service.Object.__init__(self, bus,
1102
 
                                             "/Mandos")
1103
 
            _interface = u"org.mandos_system.Mandos"
1104
 
 
 
1124
                dbus.service.Object.__init__(self, bus, "/")
 
1125
            _interface = u"se.bsnet.fukt.Mandos"
 
1126
            
1105
1127
            @dbus.service.signal(_interface, signature="oa{sv}")
1106
1128
            def ClientAdded(self, objpath, properties):
1107
1129
                "D-Bus signal"
1108
1130
                pass
1109
 
 
1110
 
            @dbus.service.signal(_interface, signature="o")
1111
 
            def ClientRemoved(self, objpath):
 
1131
            
 
1132
            @dbus.service.signal(_interface, signature="os")
 
1133
            def ClientRemoved(self, objpath, name):
1112
1134
                "D-Bus signal"
1113
1135
                pass
1114
 
 
 
1136
            
1115
1137
            @dbus.service.method(_interface, out_signature="ao")
1116
1138
            def GetAllClients(self):
 
1139
                "D-Bus method"
1117
1140
                return dbus.Array(c.dbus_object_path for c in clients)
1118
 
 
 
1141
            
1119
1142
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1120
1143
            def GetAllClientsWithProperties(self):
 
1144
                "D-Bus method"
1121
1145
                return dbus.Dictionary(
1122
1146
                    ((c.dbus_object_path, c.GetAllProperties())
1123
1147
                     for c in clients),
1124
1148
                    signature="oa{sv}")
1125
 
 
 
1149
            
1126
1150
            @dbus.service.method(_interface, in_signature="o")
1127
1151
            def RemoveClient(self, object_path):
 
1152
                "D-Bus method"
1128
1153
                for c in clients:
1129
1154
                    if c.dbus_object_path == object_path:
1130
1155
                        clients.remove(c)
1132
1157
                        c.use_dbus = False
1133
1158
                        c.disable()
1134
1159
                        # Emit D-Bus signal
1135
 
                        self.ClientRemoved(object_path)
 
1160
                        self.ClientRemoved(object_path, c.name)
1136
1161
                        return
1137
1162
                raise KeyError
1138
 
            @dbus.service.method(_interface)
1139
 
            def Quit(self):
1140
 
                main_loop.quit()
1141
 
 
 
1163
            
1142
1164
            del _interface
1143
 
    
 
1165
        
1144
1166
        mandos_server = MandosServer()
1145
1167
    
1146
1168
    for client in clients: