/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

  • Committer: Björn Påhlsson
  • Date: 2009-01-19 06:55:59 UTC
  • mto: (237.7.1 mandos) (24.1.154 mandos)
  • mto: This revision was merged to the branch mainline in revision 250.
  • Revision ID: belorn@braxen-20090119065559-vvflkj7ffxtn4ez4
fixed a bugg in mandos-ctl + added remove client option

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.8"
 
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 [%(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,
205
202
                     client lives.  %() expansions are done at
206
203
                     runtime with vars(self) as dict, so that for
207
204
                     instance %(name)s can be used in the command.
208
 
    current_checker_command: string; current running checker_command
209
205
    use_dbus: bool(); Whether to provide D-Bus interface and signals
210
206
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
211
207
    """
230
226
        if config is None:
231
227
            config = {}
232
228
        logger.debug(u"Creating client %r", self.name)
233
 
        self.use_dbus = False   # During __init__
 
229
        self.use_dbus = use_dbus
 
230
        if self.use_dbus:
 
231
            self.dbus_object_path = (dbus.ObjectPath
 
232
                                     ("/Mandos/clients/"
 
233
                                      + self.name.replace(".", "_")))
 
234
            dbus.service.Object.__init__(self, bus,
 
235
                                         self.dbus_object_path)
234
236
        # Uppercase and remove spaces from fingerprint for later
235
237
        # comparison purposes with return value from the fingerprint()
236
238
        # function
260
262
        self.disable_initiator_tag = None
261
263
        self.checker_callback_tag = None
262
264
        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)
274
265
    
275
266
    def enable(self):
276
267
        """Start this client's checker and timeout hooks"""
329
320
            # Emit D-Bus signal
330
321
            self.PropertyChanged(dbus.String(u"checker_running"),
331
322
                                 dbus.Boolean(False, variant_level=1))
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))
 
323
        if (os.WIFEXITED(condition)
 
324
            and (os.WEXITSTATUS(condition) == 0)):
 
325
            logger.info(u"Checker for %(name)s succeeded",
 
326
                        vars(self))
341
327
            if self.use_dbus:
342
328
                # Emit D-Bus signal
343
 
                self.CheckerCompleted(dbus.Int16(exitstatus),
344
 
                                      dbus.Int64(condition),
 
329
                self.CheckerCompleted(dbus.Boolean(True),
 
330
                                      dbus.UInt16(condition),
345
331
                                      dbus.String(command))
346
 
        else:
 
332
            self.bump_timeout()
 
333
        elif not os.WIFEXITED(condition):
347
334
            logger.warning(u"Checker for %(name)s crashed?",
348
335
                           vars(self))
349
336
            if self.use_dbus:
350
337
                # Emit D-Bus signal
351
 
                self.CheckerCompleted(dbus.Int16(-1),
352
 
                                      dbus.Int64(condition),
 
338
                self.CheckerCompleted(dbus.Boolean(False),
 
339
                                      dbus.UInt16(condition),
 
340
                                      dbus.String(command))
 
341
        else:
 
342
            logger.info(u"Checker for %(name)s failed",
 
343
                        vars(self))
 
344
            if self.use_dbus:
 
345
                # Emit D-Bus signal
 
346
                self.CheckerCompleted(dbus.Boolean(False),
 
347
                                      dbus.UInt16(condition),
353
348
                                      dbus.String(command))
354
349
    
355
 
    def checked_ok(self):
 
350
    def bump_timeout(self):
356
351
        """Bump up the timeout for this client.
357
352
        This should only be called when the client has been seen,
358
353
        alive and well.
381
376
        # checkers alone, the checker would have to take more time
382
377
        # than 'timeout' for the client to be declared invalid, which
383
378
        # 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
394
379
        if self.checker is None:
395
380
            try:
396
381
                # In case checker_command has exactly one % operator
406
391
                    logger.error(u'Could not format string "%s":'
407
392
                                 u' %s', self.checker_command, error)
408
393
                    return True # Try again later
409
 
                self.current_checker_command = command
410
394
            try:
411
395
                logger.info(u"Starting checker %r for %s",
412
396
                            command, self.name)
427
411
                                             (self.checker.pid,
428
412
                                              self.checker_callback,
429
413
                                              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)
436
414
            except OSError, error:
437
415
                logger.error(u"Failed to start subprocess: %s",
438
416
                             error)
471
449
            return now < (self.last_checked_ok + self.timeout)
472
450
    
473
451
    ## D-Bus methods & signals
474
 
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
452
    _interface = u"org.mandos_system.Mandos.Client"
475
453
    
476
 
    # CheckedOK - method
477
 
    CheckedOK = dbus.service.method(_interface)(checked_ok)
478
 
    CheckedOK.__name__ = "CheckedOK"
 
454
    # BumpTimeout - method
 
455
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
 
456
    BumpTimeout.__name__ = "BumpTimeout"
479
457
    
480
458
    # CheckerCompleted - signal
481
 
    @dbus.service.signal(_interface, signature="nxs")
482
 
    def CheckerCompleted(self, exitcode, waitstatus, command):
 
459
    @dbus.service.signal(_interface, signature="bqs")
 
460
    def CheckerCompleted(self, success, condition, command):
483
461
        "D-Bus signal"
484
462
        pass
485
463
    
617
595
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
618
596
        # ...do the normal thing
619
597
        return session.peer_certificate
620
 
    list_size = ctypes.c_uint(1)
 
598
    list_size = ctypes.c_uint()
621
599
    cert_list = (gnutls.library.functions
622
600
                 .gnutls_certificate_get_peers
623
601
                 (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")
627
602
    if list_size.value == 0:
628
603
        return None
629
604
    cert = cert_list[0]
698
673
        # using OpenPGP certificates.
699
674
        
700
675
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
701
 
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
702
 
        #                     "+DHE-DSS"))
 
676
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
677
        #                "+DHE-DSS"))
703
678
        # Use a fallback default, since this MUST be set.
704
679
        priority = self.server.settings.get("priority", "NORMAL")
705
680
        (gnutls.library.functions
713
688
            # Do not run session.bye() here: the session is not
714
689
            # established.  Just abandon the request.
715
690
            return
716
 
        logger.debug(u"Handshake succeeded")
717
691
        try:
718
692
            fpr = fingerprint(peer_certificate(session))
719
693
        except (TypeError, gnutls.errors.GNUTLSError), error:
721
695
            session.bye()
722
696
            return
723
697
        logger.debug(u"Fingerprint: %s", fpr)
724
 
        
725
698
        for c in self.server.clients:
726
699
            if c.fingerprint == fpr:
727
700
                client = c
740
713
            session.bye()
741
714
            return
742
715
        ## This won't work here, since we're in a fork.
743
 
        # client.checked_ok()
 
716
        # client.bump_timeout()
744
717
        sent_size = 0
745
718
        while sent_size < len(client.secret):
746
719
            sent = session.send(client.secret[sent_size:])
753
726
 
754
727
class IPv6_TCPServer(SocketServer.ForkingMixIn,
755
728
                     SocketServer.TCPServer, object):
756
 
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
 
729
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
757
730
    Attributes:
758
731
        settings:       Server settings
759
732
        clients:        Set() of Client objects
767
740
        if "clients" in kwargs:
768
741
            self.clients = kwargs["clients"]
769
742
            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"]
774
743
        self.enabled = False
775
744
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
776
745
    def server_bind(self):
790
759
                                 u" bind to interface %s",
791
760
                                 self.settings["interface"])
792
761
                else:
793
 
                    raise
 
762
                    raise error
794
763
        # Only bind(2) the socket if we really need to.
795
764
        if self.server_address[0] or self.server_address[1]:
796
765
            if not self.server_address[0]:
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,
 
766
                in6addr_any = "::"
 
767
                self.server_address = (in6addr_any,
802
768
                                       self.server_address[1])
803
769
            elif not self.server_address[1]:
804
770
                self.server_address = (self.server_address[0],
820
786
 
821
787
def string_to_delta(interval):
822
788
    """Parse a string and return a datetime.timedelta
823
 
    
 
789
 
824
790
    >>> string_to_delta('7d')
825
791
    datetime.timedelta(7)
826
792
    >>> string_to_delta('60s')
949
915
                      " files")
950
916
    parser.add_option("--no-dbus", action="store_false",
951
917
                      dest="use_dbus",
952
 
                      help=optparse.SUPPRESS_HELP) # XXX: Not done yet
953
 
    parser.add_option("--no-ipv6", action="store_false",
954
 
                      dest="use_ipv6", help="Do not use IPv6")
 
918
                      help="Do not provide D-Bus system bus"
 
919
                      " interface")
955
920
    options = parser.parse_args()[0]
956
921
    
957
922
    if options.check:
968
933
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
969
934
                        "servicename": "Mandos",
970
935
                        "use_dbus": "True",
971
 
                        "use_ipv6": "True",
972
936
                        }
973
937
    
974
938
    # Parse config file for server-global settings
977
941
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
978
942
    # Convert the SafeConfigParser object to a dict
979
943
    server_settings = server_config.defaults()
980
 
    # Use the appropriate methods on the non-string config options
981
 
    server_settings["debug"] = server_config.getboolean("DEFAULT",
982
 
                                                        "debug")
983
 
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
984
 
                                                           "use_dbus")
985
 
    server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
986
 
                                                           "use_ipv6")
987
 
    if server_settings["port"]:
988
 
        server_settings["port"] = server_config.getint("DEFAULT",
989
 
                                                       "port")
 
944
    # Use getboolean on the boolean config options
 
945
    server_settings["debug"] = (server_config.getboolean
 
946
                                ("DEFAULT", "debug"))
 
947
    server_settings["use_dbus"] = (server_config.getboolean
 
948
                                   ("DEFAULT", "use_dbus"))
990
949
    del server_config
991
950
    
992
951
    # Override the settings from the config file with command line
993
952
    # options, if set.
994
953
    for option in ("interface", "address", "port", "debug",
995
954
                   "priority", "servicename", "configdir",
996
 
                   "use_dbus", "use_ipv6"):
 
955
                   "use_dbus"):
997
956
        value = getattr(options, option)
998
957
        if value is not None:
999
958
            server_settings[option] = value
1003
962
    # For convenience
1004
963
    debug = server_settings["debug"]
1005
964
    use_dbus = server_settings["use_dbus"]
1006
 
    use_dbus = False            # XXX: Not done yet
1007
 
    use_ipv6 = server_settings["use_ipv6"]
1008
965
    
1009
966
    if not debug:
1010
967
        syslogger.setLevel(logging.WARNING)
1031
988
                                 server_settings["port"]),
1032
989
                                TCP_handler,
1033
990
                                settings=server_settings,
1034
 
                                clients=clients, use_ipv6=use_ipv6)
 
991
                                clients=clients)
1035
992
    pidfilename = "/var/run/mandos.pid"
1036
993
    try:
1037
994
        pidfile = open(pidfilename, "w")
1038
 
    except IOError:
 
995
    except IOError, error:
1039
996
        logger.error("Could not open file %r", pidfilename)
1040
997
    
1041
998
    try:
1053
1010
                uid = 65534
1054
1011
                gid = 65534
1055
1012
    try:
 
1013
        os.setuid(uid)
1056
1014
        os.setgid(gid)
1057
 
        os.setuid(uid)
1058
1015
    except OSError, error:
1059
1016
        if error[0] != errno.EPERM:
1060
1017
            raise error
1061
1018
    
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
 
    
1075
1019
    global service
1076
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1077
1020
    service = AvahiService(name = server_settings["servicename"],
1078
 
                           servicetype = "_mandos._tcp",
1079
 
                           protocol = protocol)
 
1021
                           servicetype = "_mandos._tcp", )
1080
1022
    if server_settings["interface"]:
1081
1023
        service.interface = (if_nametoindex
1082
1024
                             (server_settings["interface"]))
1093
1035
                            avahi.DBUS_INTERFACE_SERVER)
1094
1036
    # End of Avahi example code
1095
1037
    if use_dbus:
1096
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1038
        bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
 
1039
                                        bus)
1097
1040
    
1098
1041
    clients.update(Set(Client(name = section,
1099
1042
                              config
1153
1096
        class MandosServer(dbus.service.Object):
1154
1097
            """A D-Bus proxy object"""
1155
1098
            def __init__(self):
1156
 
                dbus.service.Object.__init__(self, bus, "/")
1157
 
            _interface = u"se.bsnet.fukt.Mandos"
 
1099
                dbus.service.Object.__init__(self, bus,
 
1100
                                             "/Mandos")
 
1101
            _interface = u"org.mandos_system.Mandos"
1158
1102
            
1159
1103
            @dbus.service.signal(_interface, signature="oa{sv}")
1160
1104
            def ClientAdded(self, objpath, properties):
1168
1112
            
1169
1113
            @dbus.service.method(_interface, out_signature="ao")
1170
1114
            def GetAllClients(self):
1171
 
                "D-Bus method"
1172
1115
                return dbus.Array(c.dbus_object_path for c in clients)
1173
1116
            
1174
1117
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1175
1118
            def GetAllClientsWithProperties(self):
1176
 
                "D-Bus method"
1177
1119
                return dbus.Dictionary(
1178
1120
                    ((c.dbus_object_path, c.GetAllProperties())
1179
1121
                     for c in clients),
1181
1123
            
1182
1124
            @dbus.service.method(_interface, in_signature="o")
1183
1125
            def RemoveClient(self, object_path):
1184
 
                "D-Bus method"
1185
1126
                for c in clients:
1186
1127
                    if c.dbus_object_path == object_path:
1187
1128
                        clients.remove(c)
1193
1134
                        return
1194
1135
                raise KeyError
1195
1136
            
 
1137
            @dbus.service.method(_interface, in_signature="s")
 
1138
            def RemoveClientByName(self, name):
 
1139
                for c in clients:
 
1140
                    if c.name == name:
 
1141
                        clients.remove(c)
 
1142
                        # Don't signal anything except ClientRemoved
 
1143
                        c.use_dbus = False
 
1144
                        c.disable()
 
1145
                        # Emit D-Bus signal
 
1146
                        self.ClientRemoved(c.dbus_object_path, name)
 
1147
                        return
 
1148
                raise KeyError
 
1149
            
 
1150
            @dbus.service.method(_interface)
 
1151
            def Quit(self):
 
1152
                main_loop.quit()
 
1153
            
1196
1154
            del _interface
1197
1155
        
1198
1156
        mandos_server = MandosServer()
1209
1167
    
1210
1168
    # Find out what port we got
1211
1169
    service.port = tcp_server.socket.getsockname()[1]
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())
 
1170
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
 
1171
                u" scope_id %d" % tcp_server.socket.getsockname())
1219
1172
    
1220
1173
    #service.interface = tcp_server.socket.getsockname()[3]
1221
1174
    
1241
1194
        sys.exit(1)
1242
1195
    except KeyboardInterrupt:
1243
1196
        if debug:
1244
 
            print >> sys.stderr
1245
 
        logger.debug("Server received KeyboardInterrupt")
1246
 
    logger.debug("Server exiting")
 
1197
            print
1247
1198
 
1248
1199
if __name__ == '__main__':
1249
1200
    main()