/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

* README: Update copyright year; add "2009".
* debian/copyright: - '' -
* mandos: - '' -
* mandos-clients.conf.xml: - '' -
* mandos-keygen: - '' -
* mandos-keygen.xml: - '' -
* mandos.conf.xml: - '' -
* mandos.xml: - '' -
* plugin-runner.c: - '' -
* plugin-runner.xml: - '' -
* plugins.d/askpass-fifo.c: - '' -
* plugins.d/askpass-fifo.xml: - '' -
* plugins.d/mandos-client.c: - '' -
* plugins.d/mandos-client.xml: - '' -
* plugins.d/password-prompt.c: - '' -
* plugins.d/password-prompt.xml: - '' -
* plugins.d/splashy.c: - '' -
* plugins.d/splashy.xml: - '' -
* plugins.d/usplash.c: - '' -
* plugins.d/usplash.xml: - '' -

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
 
36
36
import SocketServer
37
37
import socket
38
 
import optparse
 
38
from optparse import OptionParser
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.5"
 
69
version = "1.0.2"
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 [%(process)d]: %(levelname)s:'
77
 
                        ' %(message)s'))
 
76
                       ('Mandos: %(levelname)s: %(message)s'))
78
77
logger.addHandler(syslogger)
79
78
 
80
79
console = logging.StreamHandler()
81
 
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
82
 
                                       ' %(levelname)s: %(message)s'))
 
80
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
 
81
                                       ' %(message)s'))
83
82
logger.addHandler(console)
84
83
 
85
84
class AvahiError(Exception):
114
113
    """
115
114
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
116
115
                 servicetype = None, port = None, TXT = None,
117
 
                 domain = "", host = "", max_renames = 32768,
118
 
                 protocol = avahi.PROTO_UNSPEC):
 
116
                 domain = "", host = "", max_renames = 32768):
119
117
        self.interface = interface
120
118
        self.name = name
121
119
        self.type = servicetype
125
123
        self.host = host
126
124
        self.rename_count = 0
127
125
        self.max_renames = max_renames
128
 
        self.protocol = protocol
129
126
    def rename(self):
130
127
        """Derived from the Avahi example code"""
131
128
        if self.rename_count >= self.max_renames:
160
157
                     service.name, service.type)
161
158
        group.AddService(
162
159
                self.interface,         # interface
163
 
                self.protocol,          # protocol
 
160
                avahi.PROTO_INET6,      # protocol
164
161
                dbus.UInt32(0),         # flags
165
162
                self.name, self.type,
166
163
                self.domain, self.host,
181
178
class Client(dbus.service.Object):
182
179
    """A representation of a client host served by this server.
183
180
    Attributes:
184
 
    name:       string; from the config file, used in log messages and
185
 
                        D-Bus identifiers
 
181
    name:       string; from the config file, used in log messages
186
182
    fingerprint: string (40 or 32 hexadecimal digits); used to
187
183
                 uniquely identify the client
188
184
    secret:     bytestring; sent verbatim (over TLS) to client
229
225
        if config is None:
230
226
            config = {}
231
227
        logger.debug(u"Creating client %r", self.name)
232
 
        self.use_dbus = False   # During __init__
 
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)
233
235
        # Uppercase and remove spaces from fingerprint for later
234
236
        # comparison purposes with return value from the fingerprint()
235
237
        # function
259
261
        self.disable_initiator_tag = None
260
262
        self.checker_callback_tag = None
261
263
        self.checker_command = config["checker"]
262
 
        self.last_connect = None
263
 
        # Only now, when this client is initialized, can it show up on
264
 
        # the D-Bus
265
 
        self.use_dbus = use_dbus
266
 
        if self.use_dbus:
267
 
            self.dbus_object_path = (dbus.ObjectPath
268
 
                                     ("/clients/"
269
 
                                      + self.name.replace(".", "_")))
270
 
            dbus.service.Object.__init__(self, bus,
271
 
                                         self.dbus_object_path)
272
264
    
273
265
    def enable(self):
274
266
        """Start this client's checker and timeout hooks"""
327
319
            # Emit D-Bus signal
328
320
            self.PropertyChanged(dbus.String(u"checker_running"),
329
321
                                 dbus.Boolean(False, variant_level=1))
330
 
        if os.WIFEXITED(condition):
331
 
            exitstatus = os.WEXITSTATUS(condition)
332
 
            if exitstatus == 0:
333
 
                logger.info(u"Checker for %(name)s succeeded",
334
 
                            vars(self))
335
 
                self.checked_ok()
336
 
            else:
337
 
                logger.info(u"Checker for %(name)s failed",
338
 
                            vars(self))
 
322
        if (os.WIFEXITED(condition)
 
323
            and (os.WEXITSTATUS(condition) == 0)):
 
324
            logger.info(u"Checker for %(name)s succeeded",
 
325
                        vars(self))
339
326
            if self.use_dbus:
340
327
                # Emit D-Bus signal
341
 
                self.CheckerCompleted(dbus.Int16(exitstatus),
342
 
                                      dbus.Int64(condition),
 
328
                self.CheckerCompleted(dbus.Boolean(True),
 
329
                                      dbus.UInt16(condition),
343
330
                                      dbus.String(command))
344
 
        else:
 
331
            self.bump_timeout()
 
332
        elif not os.WIFEXITED(condition):
345
333
            logger.warning(u"Checker for %(name)s crashed?",
346
334
                           vars(self))
347
335
            if self.use_dbus:
348
336
                # Emit D-Bus signal
349
 
                self.CheckerCompleted(dbus.Int16(-1),
350
 
                                      dbus.Int64(condition),
 
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),
351
347
                                      dbus.String(command))
352
348
    
353
 
    def checked_ok(self):
 
349
    def bump_timeout(self):
354
350
        """Bump up the timeout for this client.
355
351
        This should only be called when the client has been seen,
356
352
        alive and well.
414
410
                                             (self.checker.pid,
415
411
                                              self.checker_callback,
416
412
                                              data=command))
417
 
                # The checker may have completed before the gobject
418
 
                # watch was added.  Check for this.
419
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
420
 
                if pid:
421
 
                    gobject.source_remove(self.checker_callback_tag)
422
 
                    self.checker_callback(pid, status, command)
423
413
            except OSError, error:
424
414
                logger.error(u"Failed to start subprocess: %s",
425
415
                             error)
458
448
            return now < (self.last_checked_ok + self.timeout)
459
449
    
460
450
    ## D-Bus methods & signals
461
 
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
451
    _interface = u"org.mandos_system.Mandos.Client"
462
452
    
463
 
    # CheckedOK - method
464
 
    CheckedOK = dbus.service.method(_interface)(checked_ok)
465
 
    CheckedOK.__name__ = "CheckedOK"
 
453
    # BumpTimeout - method
 
454
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
 
455
    BumpTimeout.__name__ = "BumpTimeout"
466
456
    
467
457
    # CheckerCompleted - signal
468
 
    @dbus.service.signal(_interface, signature="nxs")
469
 
    def CheckerCompleted(self, exitcode, waitstatus, command):
 
458
    @dbus.service.signal(_interface, signature="bqs")
 
459
    def CheckerCompleted(self, success, condition, command):
470
460
        "D-Bus signal"
471
461
        pass
472
462
    
513
503
                dbus.String("checker_running"):
514
504
                    dbus.Boolean(self.checker is not None,
515
505
                                 variant_level=1),
516
 
                dbus.String("object_path"):
517
 
                    dbus.ObjectPath(self.dbus_object_path,
518
 
                                    variant_level=1)
519
506
                }, signature="sv")
520
507
    
521
508
    # IsStillValid - method
604
591
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
605
592
        # ...do the normal thing
606
593
        return session.peer_certificate
607
 
    list_size = ctypes.c_uint(1)
 
594
    list_size = ctypes.c_uint()
608
595
    cert_list = (gnutls.library.functions
609
596
                 .gnutls_certificate_get_peers
610
597
                 (session._c_object, ctypes.byref(list_size)))
611
 
    if not bool(cert_list) and list_size.value != 0:
612
 
        raise gnutls.errors.GNUTLSError("error getting peer"
613
 
                                        " certificate")
614
598
    if list_size.value == 0:
615
599
        return None
616
600
    cert = cert_list[0]
685
669
        # using OpenPGP certificates.
686
670
        
687
671
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
688
 
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
689
 
        #                     "+DHE-DSS"))
 
672
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
673
        #                "+DHE-DSS"))
690
674
        # Use a fallback default, since this MUST be set.
691
675
        priority = self.server.settings.get("priority", "NORMAL")
692
676
        (gnutls.library.functions
700
684
            # Do not run session.bye() here: the session is not
701
685
            # established.  Just abandon the request.
702
686
            return
703
 
        logger.debug(u"Handshake succeeded")
704
687
        try:
705
688
            fpr = fingerprint(peer_certificate(session))
706
689
        except (TypeError, gnutls.errors.GNUTLSError), error:
708
691
            session.bye()
709
692
            return
710
693
        logger.debug(u"Fingerprint: %s", fpr)
711
 
        
712
694
        for c in self.server.clients:
713
695
            if c.fingerprint == fpr:
714
696
                client = c
727
709
            session.bye()
728
710
            return
729
711
        ## This won't work here, since we're in a fork.
730
 
        # client.checked_ok()
 
712
        # client.bump_timeout()
731
713
        sent_size = 0
732
714
        while sent_size < len(client.secret):
733
715
            sent = session.send(client.secret[sent_size:])
740
722
 
741
723
class IPv6_TCPServer(SocketServer.ForkingMixIn,
742
724
                     SocketServer.TCPServer, object):
743
 
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port.
 
725
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
744
726
    Attributes:
745
727
        settings:       Server settings
746
728
        clients:        Set() of Client objects
754
736
        if "clients" in kwargs:
755
737
            self.clients = kwargs["clients"]
756
738
            del kwargs["clients"]
757
 
        if "use_ipv6" in kwargs:
758
 
            if not kwargs["use_ipv6"]:
759
 
                self.address_family = socket.AF_INET
760
 
            del kwargs["use_ipv6"]
761
739
        self.enabled = False
762
740
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
763
741
    def server_bind(self):
777
755
                                 u" bind to interface %s",
778
756
                                 self.settings["interface"])
779
757
                else:
780
 
                    raise
 
758
                    raise error
781
759
        # Only bind(2) the socket if we really need to.
782
760
        if self.server_address[0] or self.server_address[1]:
783
761
            if not self.server_address[0]:
784
 
                if self.address_family == socket.AF_INET6:
785
 
                    any_address = "::" # in6addr_any
786
 
                else:
787
 
                    any_address = socket.INADDR_ANY
788
 
                self.server_address = (any_address,
 
762
                in6addr_any = "::"
 
763
                self.server_address = (in6addr_any,
789
764
                                       self.server_address[1])
790
765
            elif not self.server_address[1]:
791
766
                self.server_address = (self.server_address[0],
807
782
 
808
783
def string_to_delta(interval):
809
784
    """Parse a string and return a datetime.timedelta
810
 
    
 
785
 
811
786
    >>> string_to_delta('7d')
812
787
    datetime.timedelta(7)
813
788
    >>> string_to_delta('60s')
914
889
 
915
890
 
916
891
def main():
917
 
    parser = optparse.OptionParser(version = "%%prog %s" % version)
 
892
    parser = OptionParser(version = "%%prog %s" % version)
918
893
    parser.add_option("-i", "--interface", type="string",
919
894
                      metavar="IF", help="Bind to interface IF")
920
895
    parser.add_option("-a", "--address", type="string",
938
913
                      dest="use_dbus",
939
914
                      help="Do not provide D-Bus system bus"
940
915
                      " interface")
941
 
    parser.add_option("--no-ipv6", action="store_false",
942
 
                      dest="use_ipv6", help="Do not use IPv6")
943
916
    options = parser.parse_args()[0]
944
917
    
945
918
    if options.check:
956
929
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
957
930
                        "servicename": "Mandos",
958
931
                        "use_dbus": "True",
959
 
                        "use_ipv6": "True",
960
932
                        }
961
933
    
962
934
    # Parse config file for server-global settings
965
937
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
966
938
    # Convert the SafeConfigParser object to a dict
967
939
    server_settings = server_config.defaults()
968
 
    # Use the appropriate methods on the non-string config options
969
 
    server_settings["debug"] = server_config.getboolean("DEFAULT",
970
 
                                                        "debug")
971
 
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
972
 
                                                           "use_dbus")
973
 
    server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
974
 
                                                           "use_ipv6")
975
 
    if server_settings["port"]:
976
 
        server_settings["port"] = server_config.getint("DEFAULT",
977
 
                                                       "port")
 
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"))
978
945
    del server_config
979
946
    
980
947
    # Override the settings from the config file with command line
981
948
    # options, if set.
982
949
    for option in ("interface", "address", "port", "debug",
983
950
                   "priority", "servicename", "configdir",
984
 
                   "use_dbus", "use_ipv6"):
 
951
                   "use_dbus"):
985
952
        value = getattr(options, option)
986
953
        if value is not None:
987
954
            server_settings[option] = value
991
958
    # For convenience
992
959
    debug = server_settings["debug"]
993
960
    use_dbus = server_settings["use_dbus"]
994
 
    use_ipv6 = server_settings["use_ipv6"]
995
961
    
996
962
    if not debug:
997
963
        syslogger.setLevel(logging.WARNING)
1006
972
    # Parse config file with clients
1007
973
    client_defaults = { "timeout": "1h",
1008
974
                        "interval": "5m",
1009
 
                        "checker": "fping -q -- %%(host)s",
 
975
                        "checker": "fping -q -- %(host)s",
1010
976
                        "host": "",
1011
977
                        }
1012
978
    client_config = ConfigParser.SafeConfigParser(client_defaults)
1018
984
                                 server_settings["port"]),
1019
985
                                TCP_handler,
1020
986
                                settings=server_settings,
1021
 
                                clients=clients, use_ipv6=use_ipv6)
 
987
                                clients=clients)
1022
988
    pidfilename = "/var/run/mandos.pid"
1023
989
    try:
1024
990
        pidfile = open(pidfilename, "w")
1025
 
    except IOError:
 
991
    except IOError, error:
1026
992
        logger.error("Could not open file %r", pidfilename)
1027
993
    
1028
994
    try:
1029
995
        uid = pwd.getpwnam("_mandos").pw_uid
 
996
    except KeyError:
 
997
        try:
 
998
            uid = pwd.getpwnam("mandos").pw_uid
 
999
        except KeyError:
 
1000
            try:
 
1001
                uid = pwd.getpwnam("nobody").pw_uid
 
1002
            except KeyError:
 
1003
                uid = 65534
 
1004
    try:
1030
1005
        gid = pwd.getpwnam("_mandos").pw_gid
1031
1006
    except KeyError:
1032
1007
        try:
1033
 
            uid = pwd.getpwnam("mandos").pw_uid
1034
1008
            gid = pwd.getpwnam("mandos").pw_gid
1035
1009
        except KeyError:
1036
1010
            try:
1037
 
                uid = pwd.getpwnam("nobody").pw_uid
1038
1011
                gid = pwd.getpwnam("nogroup").pw_gid
1039
1012
            except KeyError:
1040
 
                uid = 65534
1041
1013
                gid = 65534
1042
1014
    try:
 
1015
        os.setuid(uid)
1043
1016
        os.setgid(gid)
1044
 
        os.setuid(uid)
1045
1017
    except OSError, error:
1046
1018
        if error[0] != errno.EPERM:
1047
1019
            raise error
1048
1020
    
1049
 
    # Enable all possible GnuTLS debugging
1050
 
    if debug:
1051
 
        # "Use a log level over 10 to enable all debugging options."
1052
 
        # - GnuTLS manual
1053
 
        gnutls.library.functions.gnutls_global_set_log_level(11)
1054
 
        
1055
 
        @gnutls.library.types.gnutls_log_func
1056
 
        def debug_gnutls(level, string):
1057
 
            logger.debug("GnuTLS: %s", string[:-1])
1058
 
        
1059
 
        (gnutls.library.functions
1060
 
         .gnutls_global_set_log_function(debug_gnutls))
1061
 
    
1062
1021
    global service
1063
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1064
1022
    service = AvahiService(name = server_settings["servicename"],
1065
 
                           servicetype = "_mandos._tcp",
1066
 
                           protocol = protocol)
 
1023
                           servicetype = "_mandos._tcp", )
1067
1024
    if server_settings["interface"]:
1068
1025
        service.interface = (if_nametoindex
1069
1026
                             (server_settings["interface"]))
1080
1037
                            avahi.DBUS_INTERFACE_SERVER)
1081
1038
    # End of Avahi example code
1082
1039
    if use_dbus:
1083
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1040
        bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
 
1041
                                        bus)
1084
1042
    
1085
1043
    clients.update(Set(Client(name = section,
1086
1044
                              config
1140
1098
        class MandosServer(dbus.service.Object):
1141
1099
            """A D-Bus proxy object"""
1142
1100
            def __init__(self):
1143
 
                dbus.service.Object.__init__(self, bus, "/")
1144
 
            _interface = u"se.bsnet.fukt.Mandos"
1145
 
            
 
1101
                dbus.service.Object.__init__(self, bus,
 
1102
                                             "/Mandos")
 
1103
            _interface = u"org.mandos_system.Mandos"
 
1104
 
1146
1105
            @dbus.service.signal(_interface, signature="oa{sv}")
1147
1106
            def ClientAdded(self, objpath, properties):
1148
1107
                "D-Bus signal"
1149
1108
                pass
1150
 
            
1151
 
            @dbus.service.signal(_interface, signature="os")
1152
 
            def ClientRemoved(self, objpath, name):
 
1109
 
 
1110
            @dbus.service.signal(_interface, signature="o")
 
1111
            def ClientRemoved(self, objpath):
1153
1112
                "D-Bus signal"
1154
1113
                pass
1155
 
            
 
1114
 
1156
1115
            @dbus.service.method(_interface, out_signature="ao")
1157
1116
            def GetAllClients(self):
1158
 
                "D-Bus method"
1159
1117
                return dbus.Array(c.dbus_object_path for c in clients)
1160
 
            
 
1118
 
1161
1119
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1162
1120
            def GetAllClientsWithProperties(self):
1163
 
                "D-Bus method"
1164
1121
                return dbus.Dictionary(
1165
1122
                    ((c.dbus_object_path, c.GetAllProperties())
1166
1123
                     for c in clients),
1167
1124
                    signature="oa{sv}")
1168
 
            
 
1125
 
1169
1126
            @dbus.service.method(_interface, in_signature="o")
1170
1127
            def RemoveClient(self, object_path):
1171
 
                "D-Bus method"
1172
1128
                for c in clients:
1173
1129
                    if c.dbus_object_path == object_path:
1174
1130
                        clients.remove(c)
1176
1132
                        c.use_dbus = False
1177
1133
                        c.disable()
1178
1134
                        # Emit D-Bus signal
1179
 
                        self.ClientRemoved(object_path, c.name)
 
1135
                        self.ClientRemoved(object_path)
1180
1136
                        return
1181
1137
                raise KeyError
1182
 
            
 
1138
            @dbus.service.method(_interface)
 
1139
            def Quit(self):
 
1140
                main_loop.quit()
 
1141
 
1183
1142
            del _interface
1184
 
        
 
1143
    
1185
1144
        mandos_server = MandosServer()
1186
1145
    
1187
1146
    for client in clients:
1196
1155
    
1197
1156
    # Find out what port we got
1198
1157
    service.port = tcp_server.socket.getsockname()[1]
1199
 
    if use_ipv6:
1200
 
        logger.info(u"Now listening on address %r, port %d,"
1201
 
                    " flowinfo %d, scope_id %d"
1202
 
                    % tcp_server.socket.getsockname())
1203
 
    else:                       # IPv4
1204
 
        logger.info(u"Now listening on address %r, port %d"
1205
 
                    % tcp_server.socket.getsockname())
 
1158
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
 
1159
                u" scope_id %d" % tcp_server.socket.getsockname())
1206
1160
    
1207
1161
    #service.interface = tcp_server.socket.getsockname()[3]
1208
1162
    
1228
1182
        sys.exit(1)
1229
1183
    except KeyboardInterrupt:
1230
1184
        if debug:
1231
 
            print >> sys.stderr
1232
 
        logger.debug("Server received KeyboardInterrupt")
1233
 
    logger.debug("Server exiting")
 
1185
            print
1234
1186
 
1235
1187
if __name__ == '__main__':
1236
1188
    main()