/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

merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008 Teddy Hogeborn
15
 
# Copyright © 2008 Björn Påhlsson
 
14
# Copyright © 2008,2009 Teddy Hogeborn
 
15
# Copyright © 2008,2009 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
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.2"
 
69
version = "1.0.5"
70
70
 
71
71
logger = logging.Logger('mandos')
72
72
syslogger = (logging.handlers.SysLogHandler
73
73
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
74
74
              address = "/dev/log"))
75
75
syslogger.setFormatter(logging.Formatter
76
 
                       ('Mandos: %(levelname)s: %(message)s'))
 
76
                       ('Mandos [%(process)d]: %(levelname)s:'
 
77
                        ' %(message)s'))
77
78
logger.addHandler(syslogger)
78
79
 
79
80
console = logging.StreamHandler()
80
 
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
81
 
                                       ' %(message)s'))
 
81
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
 
82
                                       ' %(levelname)s: %(message)s'))
82
83
logger.addHandler(console)
83
84
 
84
85
class AvahiError(Exception):
178
179
class Client(dbus.service.Object):
179
180
    """A representation of a client host served by this server.
180
181
    Attributes:
181
 
    name:       string; from the config file, used in log messages
 
182
    name:       string; from the config file, used in log messages and
 
183
                        D-Bus identifiers
182
184
    fingerprint: string (40 or 32 hexadecimal digits); used to
183
185
                 uniquely identify the client
184
186
    secret:     bytestring; sent verbatim (over TLS) to client
225
227
        if config is None:
226
228
            config = {}
227
229
        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)
 
230
        self.use_dbus = False   # During __init__
235
231
        # Uppercase and remove spaces from fingerprint for later
236
232
        # comparison purposes with return value from the fingerprint()
237
233
        # function
261
257
        self.disable_initiator_tag = None
262
258
        self.checker_callback_tag = None
263
259
        self.checker_command = config["checker"]
 
260
        self.last_connect = None
 
261
        # Only now, when this client is initialized, can it show up on
 
262
        # the D-Bus
 
263
        self.use_dbus = use_dbus
 
264
        if self.use_dbus:
 
265
            self.dbus_object_path = (dbus.ObjectPath
 
266
                                     ("/clients/"
 
267
                                      + self.name.replace(".", "_")))
 
268
            dbus.service.Object.__init__(self, bus,
 
269
                                         self.dbus_object_path)
264
270
    
265
271
    def enable(self):
266
272
        """Start this client's checker and timeout hooks"""
319
325
            # Emit D-Bus signal
320
326
            self.PropertyChanged(dbus.String(u"checker_running"),
321
327
                                 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))
 
328
        if os.WIFEXITED(condition):
 
329
            exitstatus = os.WEXITSTATUS(condition)
 
330
            if exitstatus == 0:
 
331
                logger.info(u"Checker for %(name)s succeeded",
 
332
                            vars(self))
 
333
                self.checked_ok()
 
334
            else:
 
335
                logger.info(u"Checker for %(name)s failed",
 
336
                            vars(self))
326
337
            if self.use_dbus:
327
338
                # Emit D-Bus signal
328
 
                self.CheckerCompleted(dbus.Boolean(True),
329
 
                                      dbus.UInt16(condition),
 
339
                self.CheckerCompleted(dbus.Int16(exitstatus),
 
340
                                      dbus.Int64(condition),
330
341
                                      dbus.String(command))
331
 
            self.bump_timeout()
332
 
        elif not os.WIFEXITED(condition):
 
342
        else:
333
343
            logger.warning(u"Checker for %(name)s crashed?",
334
344
                           vars(self))
335
345
            if self.use_dbus:
336
346
                # 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),
 
347
                self.CheckerCompleted(dbus.Int16(-1),
 
348
                                      dbus.Int64(condition),
347
349
                                      dbus.String(command))
348
350
    
349
 
    def bump_timeout(self):
 
351
    def checked_ok(self):
350
352
        """Bump up the timeout for this client.
351
353
        This should only be called when the client has been seen,
352
354
        alive and well.
448
450
            return now < (self.last_checked_ok + self.timeout)
449
451
    
450
452
    ## D-Bus methods & signals
451
 
    _interface = u"org.mandos_system.Mandos.Client"
 
453
    _interface = u"se.bsnet.fukt.Mandos.Client"
452
454
    
453
 
    # BumpTimeout - method
454
 
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
455
 
    BumpTimeout.__name__ = "BumpTimeout"
 
455
    # CheckedOK - method
 
456
    CheckedOK = dbus.service.method(_interface)(checked_ok)
 
457
    CheckedOK.__name__ = "CheckedOK"
456
458
    
457
459
    # CheckerCompleted - signal
458
 
    @dbus.service.signal(_interface, signature="bqs")
459
 
    def CheckerCompleted(self, success, condition, command):
 
460
    @dbus.service.signal(_interface, signature="nxs")
 
461
    def CheckerCompleted(self, exitcode, waitstatus, command):
460
462
        "D-Bus signal"
461
463
        pass
462
464
    
503
505
                dbus.String("checker_running"):
504
506
                    dbus.Boolean(self.checker is not None,
505
507
                                 variant_level=1),
 
508
                dbus.String("object_path"):
 
509
                    dbus.ObjectPath(self.dbus_object_path,
 
510
                                    variant_level=1)
506
511
                }, signature="sv")
507
512
    
508
513
    # IsStillValid - method
591
596
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
592
597
        # ...do the normal thing
593
598
        return session.peer_certificate
594
 
    list_size = ctypes.c_uint()
 
599
    list_size = ctypes.c_uint(1)
595
600
    cert_list = (gnutls.library.functions
596
601
                 .gnutls_certificate_get_peers
597
602
                 (session._c_object, ctypes.byref(list_size)))
 
603
    if not bool(cert_list) and list_size.value != 0:
 
604
        raise gnutls.errors.GNUTLSError("error getting peer"
 
605
                                        " certificate")
598
606
    if list_size.value == 0:
599
607
        return None
600
608
    cert = cert_list[0]
669
677
        # using OpenPGP certificates.
670
678
        
671
679
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
672
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
673
 
        #                "+DHE-DSS"))
 
680
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
681
        #                     "+DHE-DSS"))
674
682
        # Use a fallback default, since this MUST be set.
675
683
        priority = self.server.settings.get("priority", "NORMAL")
676
684
        (gnutls.library.functions
684
692
            # Do not run session.bye() here: the session is not
685
693
            # established.  Just abandon the request.
686
694
            return
 
695
        logger.debug(u"Handshake succeeded")
687
696
        try:
688
697
            fpr = fingerprint(peer_certificate(session))
689
698
        except (TypeError, gnutls.errors.GNUTLSError), error:
691
700
            session.bye()
692
701
            return
693
702
        logger.debug(u"Fingerprint: %s", fpr)
 
703
        
694
704
        for c in self.server.clients:
695
705
            if c.fingerprint == fpr:
696
706
                client = c
709
719
            session.bye()
710
720
            return
711
721
        ## This won't work here, since we're in a fork.
712
 
        # client.bump_timeout()
 
722
        # client.checked_ok()
713
723
        sent_size = 0
714
724
        while sent_size < len(client.secret):
715
725
            sent = session.send(client.secret[sent_size:])
755
765
                                 u" bind to interface %s",
756
766
                                 self.settings["interface"])
757
767
                else:
758
 
                    raise error
 
768
                    raise
759
769
        # Only bind(2) the socket if we really need to.
760
770
        if self.server_address[0] or self.server_address[1]:
761
771
            if not self.server_address[0]:
782
792
 
783
793
def string_to_delta(interval):
784
794
    """Parse a string and return a datetime.timedelta
785
 
 
 
795
    
786
796
    >>> string_to_delta('7d')
787
797
    datetime.timedelta(7)
788
798
    >>> string_to_delta('60s')
889
899
 
890
900
 
891
901
def main():
892
 
    parser = OptionParser(version = "%%prog %s" % version)
 
902
    parser = optparse.OptionParser(version = "%%prog %s" % version)
893
903
    parser.add_option("-i", "--interface", type="string",
894
904
                      metavar="IF", help="Bind to interface IF")
895
905
    parser.add_option("-a", "--address", type="string",
937
947
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
938
948
    # Convert the SafeConfigParser object to a dict
939
949
    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"))
 
950
    # Use the appropriate methods on the non-string config options
 
951
    server_settings["debug"] = server_config.getboolean("DEFAULT",
 
952
                                                        "debug")
 
953
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
 
954
                                                           "use_dbus")
 
955
    if server_settings["port"]:
 
956
        server_settings["port"] = server_config.getint("DEFAULT",
 
957
                                                       "port")
945
958
    del server_config
946
959
    
947
960
    # Override the settings from the config file with command line
972
985
    # Parse config file with clients
973
986
    client_defaults = { "timeout": "1h",
974
987
                        "interval": "5m",
975
 
                        "checker": "fping -q -- %(host)s",
 
988
                        "checker": "fping -q -- %%(host)s",
976
989
                        "host": "",
977
990
                        }
978
991
    client_config = ConfigParser.SafeConfigParser(client_defaults)
988
1001
    pidfilename = "/var/run/mandos.pid"
989
1002
    try:
990
1003
        pidfile = open(pidfilename, "w")
991
 
    except IOError, error:
 
1004
    except IOError:
992
1005
        logger.error("Could not open file %r", pidfilename)
993
1006
    
994
1007
    try:
995
1008
        uid = pwd.getpwnam("_mandos").pw_uid
 
1009
        gid = pwd.getpwnam("_mandos").pw_gid
996
1010
    except KeyError:
997
1011
        try:
998
1012
            uid = pwd.getpwnam("mandos").pw_uid
 
1013
            gid = pwd.getpwnam("mandos").pw_gid
999
1014
        except KeyError:
1000
1015
            try:
1001
1016
                uid = pwd.getpwnam("nobody").pw_uid
 
1017
                gid = pwd.getpwnam("nogroup").pw_gid
1002
1018
            except KeyError:
1003
1019
                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
1020
                gid = 65534
1014
1021
    try:
 
1022
        os.setgid(gid)
1015
1023
        os.setuid(uid)
1016
 
        os.setgid(gid)
1017
1024
    except OSError, error:
1018
1025
        if error[0] != errno.EPERM:
1019
1026
            raise error
1020
1027
    
 
1028
    # Enable all possible GnuTLS debugging
 
1029
    if debug:
 
1030
        # "Use a log level over 10 to enable all debugging options."
 
1031
        # - GnuTLS manual
 
1032
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
1033
        
 
1034
        @gnutls.library.types.gnutls_log_func
 
1035
        def debug_gnutls(level, string):
 
1036
            logger.debug("GnuTLS: %s", string[:-1])
 
1037
        
 
1038
        (gnutls.library.functions
 
1039
         .gnutls_global_set_log_function(debug_gnutls))
 
1040
    
1021
1041
    global service
1022
1042
    service = AvahiService(name = server_settings["servicename"],
1023
1043
                           servicetype = "_mandos._tcp", )
1037
1057
                            avahi.DBUS_INTERFACE_SERVER)
1038
1058
    # End of Avahi example code
1039
1059
    if use_dbus:
1040
 
        bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
1041
 
                                        bus)
 
1060
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1042
1061
    
1043
1062
    clients.update(Set(Client(name = section,
1044
1063
                              config
1046
1065
                              use_dbus = use_dbus)
1047
1066
                       for section in client_config.sections()))
1048
1067
    if not clients:
1049
 
        logger.critical(u"No clients defined")
1050
 
        sys.exit(1)
 
1068
        logger.warning(u"No clients defined")
1051
1069
    
1052
1070
    if debug:
1053
1071
        # Redirect stdin so all checkers get /dev/null
1099
1117
        class MandosServer(dbus.service.Object):
1100
1118
            """A D-Bus proxy object"""
1101
1119
            def __init__(self):
1102
 
                dbus.service.Object.__init__(self, bus,
1103
 
                                             "/Mandos")
1104
 
            _interface = u"org.mandos_system.Mandos"
1105
 
 
 
1120
                dbus.service.Object.__init__(self, bus, "/")
 
1121
            _interface = u"se.bsnet.fukt.Mandos"
 
1122
            
1106
1123
            @dbus.service.signal(_interface, signature="oa{sv}")
1107
1124
            def ClientAdded(self, objpath, properties):
1108
1125
                "D-Bus signal"
1109
1126
                pass
1110
 
 
1111
 
            @dbus.service.signal(_interface, signature="o")
1112
 
            def ClientRemoved(self, objpath):
 
1127
            
 
1128
            @dbus.service.signal(_interface, signature="os")
 
1129
            def ClientRemoved(self, objpath, name):
1113
1130
                "D-Bus signal"
1114
1131
                pass
1115
 
 
 
1132
            
1116
1133
            @dbus.service.method(_interface, out_signature="ao")
1117
1134
            def GetAllClients(self):
 
1135
                "D-Bus method"
1118
1136
                return dbus.Array(c.dbus_object_path for c in clients)
1119
 
 
 
1137
            
1120
1138
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1121
1139
            def GetAllClientsWithProperties(self):
 
1140
                "D-Bus method"
1122
1141
                return dbus.Dictionary(
1123
1142
                    ((c.dbus_object_path, c.GetAllProperties())
1124
1143
                     for c in clients),
1125
1144
                    signature="oa{sv}")
1126
 
 
 
1145
            
1127
1146
            @dbus.service.method(_interface, in_signature="o")
1128
1147
            def RemoveClient(self, object_path):
 
1148
                "D-Bus method"
1129
1149
                for c in clients:
1130
1150
                    if c.dbus_object_path == object_path:
1131
1151
                        clients.remove(c)
1133
1153
                        c.use_dbus = False
1134
1154
                        c.disable()
1135
1155
                        # Emit D-Bus signal
1136
 
                        self.ClientRemoved(object_path)
 
1156
                        self.ClientRemoved(object_path, c.name)
1137
1157
                        return
1138
1158
                raise KeyError
1139
 
 
 
1159
            
1140
1160
            del _interface
1141
 
    
 
1161
        
1142
1162
        mandos_server = MandosServer()
1143
1163
    
1144
1164
    for client in clients:
1180
1200
        sys.exit(1)
1181
1201
    except KeyboardInterrupt:
1182
1202
        if debug:
1183
 
            print
 
1203
            print >> sys.stderr
 
1204
        logger.debug("Server received KeyboardInterrupt")
 
1205
    logger.debug("Server exiting")
1184
1206
 
1185
1207
if __name__ == '__main__':
1186
1208
    main()