/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-03-15 04:22:14 UTC
  • mfrom: (326 mandos)
  • mto: This revision was merged to the branch mainline in revision 327.
  • Revision ID: teddy@fukt.bsnet.se-20090315042214-gvps0knkvuyewtxl
MergeĀ fromĀ trunk.

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.8"
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,
202
205
                     client lives.  %() expansions are done at
203
206
                     runtime with vars(self) as dict, so that for
204
207
                     instance %(name)s can be used in the command.
 
208
    current_checker_command: string; current running checker_command
205
209
    use_dbus: bool(); Whether to provide D-Bus interface and signals
206
210
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
207
211
    """
256
260
        self.disable_initiator_tag = None
257
261
        self.checker_callback_tag = None
258
262
        self.checker_command = config["checker"]
 
263
        self.current_checker_command = None
259
264
        self.last_connect = None
260
265
        # Only now, when this client is initialized, can it show up on
261
266
        # the D-Bus
376
381
        # checkers alone, the checker would have to take more time
377
382
        # than 'timeout' for the client to be declared invalid, which
378
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
379
394
        if self.checker is None:
380
395
            try:
381
396
                # In case checker_command has exactly one % operator
391
406
                    logger.error(u'Could not format string "%s":'
392
407
                                 u' %s', self.checker_command, error)
393
408
                    return True # Try again later
 
409
                self.current_checker_command = command
394
410
            try:
395
411
                logger.info(u"Starting checker %r for %s",
396
412
                            command, self.name)
411
427
                                             (self.checker.pid,
412
428
                                              self.checker_callback,
413
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)
414
436
            except OSError, error:
415
437
                logger.error(u"Failed to start subprocess: %s",
416
438
                             error)
687
709
            (gnutls.library.functions
688
710
             .gnutls_priority_set_direct(session._c_object,
689
711
                                         priority, None))
690
 
 
 
712
            
691
713
            try:
692
714
                session.handshake()
693
715
            except gnutls.errors.GNUTLSError, error:
703
725
                session.bye()
704
726
                return
705
727
            logger.debug(u"Fingerprint: %s", fpr)
 
728
            
706
729
            for c in self.server.clients:
707
730
                if c.fingerprint == fpr:
708
731
                    client = c
723
746
                session.bye()
724
747
                return
725
748
            ipc.write("SENDING %s\n" % client.name)
726
 
            ## This won't work here, since we're in a fork.
727
 
            # client.checked_ok()
728
749
            sent_size = 0
729
750
            while sent_size < len(client.secret):
730
751
                sent = session.send(client.secret[sent_size:])
759
780
 
760
781
class IPv6_TCPServer(ForkingMixInWithPipe,
761
782
                     SocketServer.TCPServer, object):
762
 
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
783
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
763
784
    Attributes:
764
785
        settings:       Server settings
765
786
        clients:        Set() of Client objects
773
794
        if "clients" in kwargs:
774
795
            self.clients = kwargs["clients"]
775
796
            del kwargs["clients"]
 
797
        if "use_ipv6" in kwargs:
 
798
            if not kwargs["use_ipv6"]:
 
799
                self.address_family = socket.AF_INET
 
800
            del kwargs["use_ipv6"]
776
801
        self.enabled = False
777
802
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
778
803
    def server_bind(self):
792
817
                                 u" bind to interface %s",
793
818
                                 self.settings["interface"])
794
819
                else:
795
 
                    raise error
 
820
                    raise
796
821
        # Only bind(2) the socket if we really need to.
797
822
        if self.server_address[0] or self.server_address[1]:
798
823
            if not self.server_address[0]:
799
 
                in6addr_any = "::"
800
 
                self.server_address = (in6addr_any,
 
824
                if self.address_family == socket.AF_INET6:
 
825
                    any_address = "::" # in6addr_any
 
826
                else:
 
827
                    any_address = socket.INADDR_ANY
 
828
                self.server_address = (any_address,
801
829
                                       self.server_address[1])
802
830
            elif not self.server_address[1]:
803
831
                self.server_address = (self.server_address[0],
843
871
            pass                # xxx
844
872
        elif cmd == "SENDING":
845
873
            pass                # xxx
 
874
            # client.checked_ok()
846
875
        else:
847
876
            logger.error("Unknown IPC command: %r", cmdline)
848
877
        
852
881
 
853
882
def string_to_delta(interval):
854
883
    """Parse a string and return a datetime.timedelta
855
 
 
 
884
    
856
885
    >>> string_to_delta('7d')
857
886
    datetime.timedelta(7)
858
887
    >>> string_to_delta('60s')
983
1012
                      dest="use_dbus",
984
1013
                      help="Do not provide D-Bus system bus"
985
1014
                      " interface")
 
1015
    parser.add_option("--no-ipv6", action="store_false",
 
1016
                      dest="use_ipv6", help="Do not use IPv6")
986
1017
    options = parser.parse_args()[0]
987
1018
    
988
1019
    if options.check:
999
1030
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1000
1031
                        "servicename": "Mandos",
1001
1032
                        "use_dbus": "True",
 
1033
                        "use_ipv6": "True",
1002
1034
                        }
1003
1035
    
1004
1036
    # Parse config file for server-global settings
1012
1044
                                                        "debug")
1013
1045
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1014
1046
                                                           "use_dbus")
 
1047
    server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
 
1048
                                                           "use_ipv6")
1015
1049
    if server_settings["port"]:
1016
1050
        server_settings["port"] = server_config.getint("DEFAULT",
1017
1051
                                                       "port")
1021
1055
    # options, if set.
1022
1056
    for option in ("interface", "address", "port", "debug",
1023
1057
                   "priority", "servicename", "configdir",
1024
 
                   "use_dbus"):
 
1058
                   "use_dbus", "use_ipv6"):
1025
1059
        value = getattr(options, option)
1026
1060
        if value is not None:
1027
1061
            server_settings[option] = value
1031
1065
    # For convenience
1032
1066
    debug = server_settings["debug"]
1033
1067
    use_dbus = server_settings["use_dbus"]
 
1068
    use_ipv6 = server_settings["use_ipv6"]
1034
1069
    
1035
1070
    if not debug:
1036
1071
        syslogger.setLevel(logging.WARNING)
1057
1092
                                 server_settings["port"]),
1058
1093
                                TCP_handler,
1059
1094
                                settings=server_settings,
1060
 
                                clients=clients)
 
1095
                                clients=clients, use_ipv6=use_ipv6)
1061
1096
    pidfilename = "/var/run/mandos.pid"
1062
1097
    try:
1063
1098
        pidfile = open(pidfilename, "w")
1064
 
    except IOError, error:
 
1099
    except IOError:
1065
1100
        logger.error("Could not open file %r", pidfilename)
1066
1101
    
1067
1102
    try:
1079
1114
                uid = 65534
1080
1115
                gid = 65534
1081
1116
    try:
 
1117
        os.setgid(gid)
1082
1118
        os.setuid(uid)
1083
 
        os.setgid(gid)
1084
1119
    except OSError, error:
1085
1120
        if error[0] != errno.EPERM:
1086
1121
            raise error
1087
1122
    
 
1123
    # Enable all possible GnuTLS debugging
 
1124
    if debug:
 
1125
        # "Use a log level over 10 to enable all debugging options."
 
1126
        # - GnuTLS manual
 
1127
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
1128
        
 
1129
        @gnutls.library.types.gnutls_log_func
 
1130
        def debug_gnutls(level, string):
 
1131
            logger.debug("GnuTLS: %s", string[:-1])
 
1132
        
 
1133
        (gnutls.library.functions
 
1134
         .gnutls_global_set_log_function(debug_gnutls))
 
1135
    
1088
1136
    global service
 
1137
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1089
1138
    service = AvahiService(name = server_settings["servicename"],
1090
 
                           servicetype = "_mandos._tcp", )
 
1139
                           servicetype = "_mandos._tcp",
 
1140
                           protocol = protocol)
1091
1141
    if server_settings["interface"]:
1092
1142
        service.interface = (if_nametoindex
1093
1143
                             (server_settings["interface"]))
1220
1270
    
1221
1271
    # Find out what port we got
1222
1272
    service.port = tcp_server.socket.getsockname()[1]
1223
 
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
1224
 
                u" scope_id %d" % tcp_server.socket.getsockname())
 
1273
    if use_ipv6:
 
1274
        logger.info(u"Now listening on address %r, port %d,"
 
1275
                    " flowinfo %d, scope_id %d"
 
1276
                    % tcp_server.socket.getsockname())
 
1277
    else:                       # IPv4
 
1278
        logger.info(u"Now listening on address %r, port %d"
 
1279
                    % tcp_server.socket.getsockname())
1225
1280
    
1226
1281
    #service.interface = tcp_server.socket.getsockname()[3]
1227
1282
    
1247
1302
        sys.exit(1)
1248
1303
    except KeyboardInterrupt:
1249
1304
        if debug:
1250
 
            print
 
1305
            print >> sys.stderr
 
1306
        logger.debug("Server received KeyboardInterrupt")
 
1307
    logger.debug("Server exiting")
1251
1308
 
1252
1309
if __name__ == '__main__':
1253
1310
    main()