/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-02-15 09:09:27 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090215090927-sij7pzw24jg5njdy
* Makefile (common.ent): Update "version" entity correctly.

* mandos-clients.conf.xml (OPTIONS): Corrected old mistake.

Show diffs side-by-side

added added

removed removed

Lines of Context:
66
66
import ctypes
67
67
import ctypes.util
68
68
 
69
 
version = "1.0.5"
 
69
version = "1.0.6"
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):
113
114
    """
114
115
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
115
116
                 servicetype = None, port = None, TXT = None,
116
 
                 domain = "", host = "", max_renames = 32768):
 
117
                 domain = "", host = "", max_renames = 32768,
 
118
                 protocol = avahi.PROTO_UNSPEC):
117
119
        self.interface = interface
118
120
        self.name = name
119
121
        self.type = servicetype
123
125
        self.host = host
124
126
        self.rename_count = 0
125
127
        self.max_renames = max_renames
 
128
        self.protocol = protocol
126
129
    def rename(self):
127
130
        """Derived from the Avahi example code"""
128
131
        if self.rename_count >= self.max_renames:
157
160
                     service.name, service.type)
158
161
        group.AddService(
159
162
                self.interface,         # interface
160
 
                avahi.PROTO_INET6,      # protocol
 
163
                self.protocol,          # protocol
161
164
                dbus.UInt32(0),         # flags
162
165
                self.name, self.type,
163
166
                self.domain, self.host,
178
181
class Client(dbus.service.Object):
179
182
    """A representation of a client host served by this server.
180
183
    Attributes:
181
 
    name:       string; from the config file, used in log messages
 
184
    name:       string; from the config file, used in log messages and
 
185
                        D-Bus identifiers
182
186
    fingerprint: string (40 or 32 hexadecimal digits); used to
183
187
                 uniquely identify the client
184
188
    secret:     bytestring; sent verbatim (over TLS) to client
201
205
                     client lives.  %() expansions are done at
202
206
                     runtime with vars(self) as dict, so that for
203
207
                     instance %(name)s can be used in the command.
 
208
    current_checker_command: string; current running checker_command
204
209
    use_dbus: bool(); Whether to provide D-Bus interface and signals
205
210
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
206
211
    """
225
230
        if config is None:
226
231
            config = {}
227
232
        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)
 
233
        self.use_dbus = False   # During __init__
235
234
        # Uppercase and remove spaces from fingerprint for later
236
235
        # comparison purposes with return value from the fingerprint()
237
236
        # function
261
260
        self.disable_initiator_tag = None
262
261
        self.checker_callback_tag = None
263
262
        self.checker_command = config["checker"]
 
263
        self.current_checker_command = None
 
264
        self.last_connect = None
 
265
        # Only now, when this client is initialized, can it show up on
 
266
        # the D-Bus
 
267
        self.use_dbus = use_dbus
 
268
        if self.use_dbus:
 
269
            self.dbus_object_path = (dbus.ObjectPath
 
270
                                     ("/clients/"
 
271
                                      + self.name.replace(".", "_")))
 
272
            dbus.service.Object.__init__(self, bus,
 
273
                                         self.dbus_object_path)
264
274
    
265
275
    def enable(self):
266
276
        """Start this client's checker and timeout hooks"""
319
329
            # Emit D-Bus signal
320
330
            self.PropertyChanged(dbus.String(u"checker_running"),
321
331
                                 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))
 
332
        if os.WIFEXITED(condition):
 
333
            exitstatus = os.WEXITSTATUS(condition)
 
334
            if exitstatus == 0:
 
335
                logger.info(u"Checker for %(name)s succeeded",
 
336
                            vars(self))
 
337
                self.checked_ok()
 
338
            else:
 
339
                logger.info(u"Checker for %(name)s failed",
 
340
                            vars(self))
326
341
            if self.use_dbus:
327
342
                # Emit D-Bus signal
328
 
                self.CheckerCompleted(dbus.Boolean(True),
329
 
                                      dbus.UInt16(condition),
 
343
                self.CheckerCompleted(dbus.Int16(exitstatus),
 
344
                                      dbus.Int64(condition),
330
345
                                      dbus.String(command))
331
 
            self.bump_timeout()
332
 
        elif not os.WIFEXITED(condition):
 
346
        else:
333
347
            logger.warning(u"Checker for %(name)s crashed?",
334
348
                           vars(self))
335
349
            if self.use_dbus:
336
350
                # 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),
 
351
                self.CheckerCompleted(dbus.Int16(-1),
 
352
                                      dbus.Int64(condition),
347
353
                                      dbus.String(command))
348
354
    
349
 
    def bump_timeout(self):
 
355
    def checked_ok(self):
350
356
        """Bump up the timeout for this client.
351
357
        This should only be called when the client has been seen,
352
358
        alive and well.
375
381
        # checkers alone, the checker would have to take more time
376
382
        # than 'timeout' for the client to be declared invalid, which
377
383
        # is as it should be.
 
384
        
 
385
        # If a checker exists, make sure it is not a zombie
 
386
        if self.checker is not None:
 
387
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
388
            if pid:
 
389
                logger.warning("Checker was a zombie")
 
390
                gobject.source_remove(self.checker_callback_tag)
 
391
                self.checker_callback(pid, status,
 
392
                                      self.current_checker_command)
 
393
        # Start a new checker if needed
378
394
        if self.checker is None:
379
395
            try:
380
396
                # In case checker_command has exactly one % operator
390
406
                    logger.error(u'Could not format string "%s":'
391
407
                                 u' %s', self.checker_command, error)
392
408
                    return True # Try again later
 
409
                self.current_checker_command = command
393
410
            try:
394
411
                logger.info(u"Starting checker %r for %s",
395
412
                            command, self.name)
410
427
                                             (self.checker.pid,
411
428
                                              self.checker_callback,
412
429
                                              data=command))
 
430
                # The checker may have completed before the gobject
 
431
                # watch was added.  Check for this.
 
432
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
433
                if pid:
 
434
                    gobject.source_remove(self.checker_callback_tag)
 
435
                    self.checker_callback(pid, status, command)
413
436
            except OSError, error:
414
437
                logger.error(u"Failed to start subprocess: %s",
415
438
                             error)
448
471
            return now < (self.last_checked_ok + self.timeout)
449
472
    
450
473
    ## D-Bus methods & signals
451
 
    _interface = u"org.mandos_system.Mandos.Client"
 
474
    _interface = u"se.bsnet.fukt.Mandos.Client"
452
475
    
453
 
    # BumpTimeout - method
454
 
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
455
 
    BumpTimeout.__name__ = "BumpTimeout"
 
476
    # CheckedOK - method
 
477
    CheckedOK = dbus.service.method(_interface)(checked_ok)
 
478
    CheckedOK.__name__ = "CheckedOK"
456
479
    
457
480
    # CheckerCompleted - signal
458
 
    @dbus.service.signal(_interface, signature="bqs")
459
 
    def CheckerCompleted(self, success, condition, command):
 
481
    @dbus.service.signal(_interface, signature="nxs")
 
482
    def CheckerCompleted(self, exitcode, waitstatus, command):
460
483
        "D-Bus signal"
461
484
        pass
462
485
    
503
526
                dbus.String("checker_running"):
504
527
                    dbus.Boolean(self.checker is not None,
505
528
                                 variant_level=1),
 
529
                dbus.String("object_path"):
 
530
                    dbus.ObjectPath(self.dbus_object_path,
 
531
                                    variant_level=1)
506
532
                }, signature="sv")
507
533
    
508
534
    # IsStillValid - method
591
617
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
592
618
        # ...do the normal thing
593
619
        return session.peer_certificate
594
 
    list_size = ctypes.c_uint()
 
620
    list_size = ctypes.c_uint(1)
595
621
    cert_list = (gnutls.library.functions
596
622
                 .gnutls_certificate_get_peers
597
623
                 (session._c_object, ctypes.byref(list_size)))
 
624
    if not bool(cert_list) and list_size.value != 0:
 
625
        raise gnutls.errors.GNUTLSError("error getting peer"
 
626
                                        " certificate")
598
627
    if list_size.value == 0:
599
628
        return None
600
629
    cert = cert_list[0]
669
698
        # using OpenPGP certificates.
670
699
        
671
700
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
672
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
673
 
        #                "+DHE-DSS"))
 
701
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
702
        #                     "+DHE-DSS"))
674
703
        # Use a fallback default, since this MUST be set.
675
704
        priority = self.server.settings.get("priority", "NORMAL")
676
705
        (gnutls.library.functions
684
713
            # Do not run session.bye() here: the session is not
685
714
            # established.  Just abandon the request.
686
715
            return
 
716
        logger.debug(u"Handshake succeeded")
687
717
        try:
688
718
            fpr = fingerprint(peer_certificate(session))
689
719
        except (TypeError, gnutls.errors.GNUTLSError), error:
691
721
            session.bye()
692
722
            return
693
723
        logger.debug(u"Fingerprint: %s", fpr)
 
724
        
694
725
        for c in self.server.clients:
695
726
            if c.fingerprint == fpr:
696
727
                client = c
709
740
            session.bye()
710
741
            return
711
742
        ## This won't work here, since we're in a fork.
712
 
        # client.bump_timeout()
 
743
        # client.checked_ok()
713
744
        sent_size = 0
714
745
        while sent_size < len(client.secret):
715
746
            sent = session.send(client.secret[sent_size:])
722
753
 
723
754
class IPv6_TCPServer(SocketServer.ForkingMixIn,
724
755
                     SocketServer.TCPServer, object):
725
 
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
756
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
726
757
    Attributes:
727
758
        settings:       Server settings
728
759
        clients:        Set() of Client objects
736
767
        if "clients" in kwargs:
737
768
            self.clients = kwargs["clients"]
738
769
            del kwargs["clients"]
 
770
        if "use_ipv6" in kwargs:
 
771
            if not kwargs["use_ipv6"]:
 
772
                self.address_family = socket.AF_INET
 
773
            del kwargs["use_ipv6"]
739
774
        self.enabled = False
740
775
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
741
776
    def server_bind(self):
755
790
                                 u" bind to interface %s",
756
791
                                 self.settings["interface"])
757
792
                else:
758
 
                    raise error
 
793
                    raise
759
794
        # Only bind(2) the socket if we really need to.
760
795
        if self.server_address[0] or self.server_address[1]:
761
796
            if not self.server_address[0]:
762
 
                in6addr_any = "::"
763
 
                self.server_address = (in6addr_any,
 
797
                if self.address_family == socket.AF_INET6:
 
798
                    any_address = "::" # in6addr_any
 
799
                else:
 
800
                    any_address = socket.INADDR_ANY
 
801
                self.server_address = (any_address,
764
802
                                       self.server_address[1])
765
803
            elif not self.server_address[1]:
766
804
                self.server_address = (self.server_address[0],
782
820
 
783
821
def string_to_delta(interval):
784
822
    """Parse a string and return a datetime.timedelta
785
 
 
 
823
    
786
824
    >>> string_to_delta('7d')
787
825
    datetime.timedelta(7)
788
826
    >>> string_to_delta('60s')
913
951
                      dest="use_dbus",
914
952
                      help="Do not provide D-Bus system bus"
915
953
                      " interface")
 
954
    parser.add_option("--no-ipv6", action="store_false",
 
955
                      dest="use_ipv6", help="Do not use IPv6")
916
956
    options = parser.parse_args()[0]
917
957
    
918
958
    if options.check:
929
969
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
930
970
                        "servicename": "Mandos",
931
971
                        "use_dbus": "True",
 
972
                        "use_ipv6": "True",
932
973
                        }
933
974
    
934
975
    # Parse config file for server-global settings
937
978
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
938
979
    # Convert the SafeConfigParser object to a dict
939
980
    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"))
 
981
    # Use the appropriate methods on the non-string config options
 
982
    server_settings["debug"] = server_config.getboolean("DEFAULT",
 
983
                                                        "debug")
 
984
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
 
985
                                                           "use_dbus")
 
986
    server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
 
987
                                                           "use_ipv6")
 
988
    if server_settings["port"]:
 
989
        server_settings["port"] = server_config.getint("DEFAULT",
 
990
                                                       "port")
945
991
    del server_config
946
992
    
947
993
    # Override the settings from the config file with command line
948
994
    # options, if set.
949
995
    for option in ("interface", "address", "port", "debug",
950
996
                   "priority", "servicename", "configdir",
951
 
                   "use_dbus"):
 
997
                   "use_dbus", "use_ipv6"):
952
998
        value = getattr(options, option)
953
999
        if value is not None:
954
1000
            server_settings[option] = value
958
1004
    # For convenience
959
1005
    debug = server_settings["debug"]
960
1006
    use_dbus = server_settings["use_dbus"]
 
1007
    use_ipv6 = server_settings["use_ipv6"]
961
1008
    
962
1009
    if not debug:
963
1010
        syslogger.setLevel(logging.WARNING)
984
1031
                                 server_settings["port"]),
985
1032
                                TCP_handler,
986
1033
                                settings=server_settings,
987
 
                                clients=clients)
 
1034
                                clients=clients, use_ipv6=use_ipv6)
988
1035
    pidfilename = "/var/run/mandos.pid"
989
1036
    try:
990
1037
        pidfile = open(pidfilename, "w")
991
 
    except IOError, error:
 
1038
    except IOError:
992
1039
        logger.error("Could not open file %r", pidfilename)
993
1040
    
994
1041
    try:
1006
1053
                uid = 65534
1007
1054
                gid = 65534
1008
1055
    try:
 
1056
        os.setgid(gid)
1009
1057
        os.setuid(uid)
1010
 
        os.setgid(gid)
1011
1058
    except OSError, error:
1012
1059
        if error[0] != errno.EPERM:
1013
1060
            raise error
1014
1061
    
 
1062
    # Enable all possible GnuTLS debugging
 
1063
    if debug:
 
1064
        # "Use a log level over 10 to enable all debugging options."
 
1065
        # - GnuTLS manual
 
1066
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
1067
        
 
1068
        @gnutls.library.types.gnutls_log_func
 
1069
        def debug_gnutls(level, string):
 
1070
            logger.debug("GnuTLS: %s", string[:-1])
 
1071
        
 
1072
        (gnutls.library.functions
 
1073
         .gnutls_global_set_log_function(debug_gnutls))
 
1074
    
1015
1075
    global service
 
1076
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1016
1077
    service = AvahiService(name = server_settings["servicename"],
1017
 
                           servicetype = "_mandos._tcp", )
 
1078
                           servicetype = "_mandos._tcp",
 
1079
                           protocol = protocol)
1018
1080
    if server_settings["interface"]:
1019
1081
        service.interface = (if_nametoindex
1020
1082
                             (server_settings["interface"]))
1031
1093
                            avahi.DBUS_INTERFACE_SERVER)
1032
1094
    # End of Avahi example code
1033
1095
    if use_dbus:
1034
 
        bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
1035
 
                                        bus)
 
1096
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1036
1097
    
1037
1098
    clients.update(Set(Client(name = section,
1038
1099
                              config
1092
1153
        class MandosServer(dbus.service.Object):
1093
1154
            """A D-Bus proxy object"""
1094
1155
            def __init__(self):
1095
 
                dbus.service.Object.__init__(self, bus,
1096
 
                                             "/Mandos")
1097
 
            _interface = u"org.mandos_system.Mandos"
1098
 
 
 
1156
                dbus.service.Object.__init__(self, bus, "/")
 
1157
            _interface = u"se.bsnet.fukt.Mandos"
 
1158
            
1099
1159
            @dbus.service.signal(_interface, signature="oa{sv}")
1100
1160
            def ClientAdded(self, objpath, properties):
1101
1161
                "D-Bus signal"
1102
1162
                pass
1103
 
 
1104
 
            @dbus.service.signal(_interface, signature="o")
1105
 
            def ClientRemoved(self, objpath):
 
1163
            
 
1164
            @dbus.service.signal(_interface, signature="os")
 
1165
            def ClientRemoved(self, objpath, name):
1106
1166
                "D-Bus signal"
1107
1167
                pass
1108
 
 
 
1168
            
1109
1169
            @dbus.service.method(_interface, out_signature="ao")
1110
1170
            def GetAllClients(self):
 
1171
                "D-Bus method"
1111
1172
                return dbus.Array(c.dbus_object_path for c in clients)
1112
 
 
 
1173
            
1113
1174
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1114
1175
            def GetAllClientsWithProperties(self):
 
1176
                "D-Bus method"
1115
1177
                return dbus.Dictionary(
1116
1178
                    ((c.dbus_object_path, c.GetAllProperties())
1117
1179
                     for c in clients),
1118
1180
                    signature="oa{sv}")
1119
 
 
 
1181
            
1120
1182
            @dbus.service.method(_interface, in_signature="o")
1121
1183
            def RemoveClient(self, object_path):
 
1184
                "D-Bus method"
1122
1185
                for c in clients:
1123
1186
                    if c.dbus_object_path == object_path:
1124
1187
                        clients.remove(c)
1126
1189
                        c.use_dbus = False
1127
1190
                        c.disable()
1128
1191
                        # Emit D-Bus signal
1129
 
                        self.ClientRemoved(object_path)
 
1192
                        self.ClientRemoved(object_path, c.name)
1130
1193
                        return
1131
1194
                raise KeyError
1132
 
            @dbus.service.method(_interface)
1133
 
            def Quit(self):
1134
 
                main_loop.quit()
1135
 
 
 
1195
            
1136
1196
            del _interface
1137
 
    
 
1197
        
1138
1198
        mandos_server = MandosServer()
1139
1199
    
1140
1200
    for client in clients:
1149
1209
    
1150
1210
    # Find out what port we got
1151
1211
    service.port = tcp_server.socket.getsockname()[1]
1152
 
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
1153
 
                u" scope_id %d" % tcp_server.socket.getsockname())
 
1212
    if use_ipv6:
 
1213
        logger.info(u"Now listening on address %r, port %d,"
 
1214
                    " flowinfo %d, scope_id %d"
 
1215
                    % tcp_server.socket.getsockname())
 
1216
    else:                       # IPv4
 
1217
        logger.info(u"Now listening on address %r, port %d"
 
1218
                    % tcp_server.socket.getsockname())
1154
1219
    
1155
1220
    #service.interface = tcp_server.socket.getsockname()[3]
1156
1221
    
1176
1241
        sys.exit(1)
1177
1242
    except KeyboardInterrupt:
1178
1243
        if debug:
1179
 
            print
 
1244
            print >> sys.stderr
 
1245
        logger.debug("Server received KeyboardInterrupt")
 
1246
    logger.debug("Server exiting")
1180
1247
 
1181
1248
if __name__ == '__main__':
1182
1249
    main()