/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-24 13:52:18 UTC
  • mfrom: (237.2.16 mandos-release)
  • Revision ID: teddy@fukt.bsnet.se-20090224135218-kl93eq7slwm9gxc2
Merge from release branch.

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.7"
70
70
 
71
71
logger = logging.Logger('mandos')
72
72
syslogger = (logging.handlers.SysLogHandler
137
137
        logger.info(u"Changing Zeroconf service name to %r ...",
138
138
                    str(self.name))
139
139
        syslogger.setFormatter(logging.Formatter
140
 
                               ('Mandos (%s) [%%(process)d]:'
141
 
                                ' %%(levelname)s: %%(message)s'
142
 
                                % self.name))
 
140
                               ('Mandos (%s): %%(levelname)s:'
 
141
                                ' %%(message)s' % self.name))
143
142
        self.remove()
144
143
        self.add()
145
144
        self.rename_count += 1
543
542
        "D-Bus signal"
544
543
        pass
545
544
    
546
 
    # ReceivedSecret - signal
547
 
    @dbus.service.signal(_interface)
548
 
    def ReceivedSecret(self):
549
 
        "D-Bus signal"
550
 
        pass
551
 
    
552
 
    # Rejected - signal
553
 
    @dbus.service.signal(_interface)
554
 
    def Rejected(self):
555
 
        "D-Bus signal"
556
 
        pass
557
 
    
558
545
    # SetChecker - method
559
546
    @dbus.service.method(_interface, in_signature="s")
560
547
    def SetChecker(self, checker):
691
678
    def handle(self):
692
679
        logger.info(u"TCP connection from: %s",
693
680
                    unicode(self.client_address))
694
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
695
 
        # Open IPC pipe to parent process
696
 
        with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
697
 
            session = (gnutls.connection
698
 
                       .ClientSession(self.request,
699
 
                                      gnutls.connection
700
 
                                      .X509Credentials()))
701
 
            
702
 
            line = self.request.makefile().readline()
703
 
            logger.debug(u"Protocol version: %r", line)
704
 
            try:
705
 
                if int(line.strip().split()[0]) > 1:
706
 
                    raise RuntimeError
707
 
            except (ValueError, IndexError, RuntimeError), error:
708
 
                logger.error(u"Unknown protocol version: %s", error)
709
 
                return
710
 
            
711
 
            # Note: gnutls.connection.X509Credentials is really a
712
 
            # generic GnuTLS certificate credentials object so long as
713
 
            # no X.509 keys are added to it.  Therefore, we can use it
714
 
            # here despite using OpenPGP certificates.
715
 
            
716
 
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
717
 
            #                     "+AES-256-CBC", "+SHA1",
718
 
            #                     "+COMP-NULL", "+CTYPE-OPENPGP",
719
 
            #                     "+DHE-DSS"))
720
 
            # Use a fallback default, since this MUST be set.
721
 
            priority = self.server.settings.get("priority", "NORMAL")
722
 
            (gnutls.library.functions
723
 
             .gnutls_priority_set_direct(session._c_object,
724
 
                                         priority, None))
725
 
            
726
 
            try:
727
 
                session.handshake()
728
 
            except gnutls.errors.GNUTLSError, error:
729
 
                logger.warning(u"Handshake failed: %s", error)
730
 
                # Do not run session.bye() here: the session is not
731
 
                # established.  Just abandon the request.
732
 
                return
733
 
            logger.debug(u"Handshake succeeded")
734
 
            try:
735
 
                fpr = fingerprint(peer_certificate(session))
736
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
737
 
                logger.warning(u"Bad certificate: %s", error)
738
 
                session.bye()
739
 
                return
740
 
            logger.debug(u"Fingerprint: %s", fpr)
741
 
            
742
 
            for c in self.server.clients:
743
 
                if c.fingerprint == fpr:
744
 
                    client = c
745
 
                    break
746
 
            else:
747
 
                logger.warning(u"Client not found for fingerprint: %s",
748
 
                               fpr)
749
 
                ipc.write("NOTFOUND %s\n" % fpr)
750
 
                session.bye()
751
 
                return
752
 
            # Have to check if client.still_valid(), since it is
753
 
            # possible that the client timed out while establishing
754
 
            # the GnuTLS session.
755
 
            if not client.still_valid():
756
 
                logger.warning(u"Client %(name)s is invalid",
757
 
                               vars(client))
758
 
                ipc.write("INVALID %s\n" % client.name)
759
 
                session.bye()
760
 
                return
761
 
            ipc.write("SENDING %s\n" % client.name)
762
 
            sent_size = 0
763
 
            while sent_size < len(client.secret):
764
 
                sent = session.send(client.secret[sent_size:])
765
 
                logger.debug(u"Sent: %d, remaining: %d",
766
 
                             sent, len(client.secret)
767
 
                             - (sent_size + sent))
768
 
                sent_size += sent
769
 
            session.bye()
770
 
 
771
 
 
772
 
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
773
 
    """Like SocketServer.ForkingMixIn, but also pass a pipe.
774
 
    Assumes a gobject.MainLoop event loop.
775
 
    """
776
 
    def process_request(self, request, client_address):
777
 
        """This overrides and wraps the original process_request().
778
 
        This function creates a new pipe in self.pipe 
779
 
        """
780
 
        self.pipe = os.pipe()
781
 
        super(ForkingMixInWithPipe,
782
 
              self).process_request(request, client_address)
783
 
        os.close(self.pipe[1])  # close write end
784
 
        # Call "handle_ipc" for both data and EOF events
785
 
        gobject.io_add_watch(self.pipe[0],
786
 
                             gobject.IO_IN | gobject.IO_HUP,
787
 
                             self.handle_ipc)
788
 
    def handle_ipc(source, condition):
789
 
        """Dummy function; override as necessary"""
790
 
        os.close(source)
791
 
        return False
792
 
 
793
 
 
794
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
681
        session = (gnutls.connection
 
682
                   .ClientSession(self.request,
 
683
                                  gnutls.connection
 
684
                                  .X509Credentials()))
 
685
        
 
686
        line = self.request.makefile().readline()
 
687
        logger.debug(u"Protocol version: %r", line)
 
688
        try:
 
689
            if int(line.strip().split()[0]) > 1:
 
690
                raise RuntimeError
 
691
        except (ValueError, IndexError, RuntimeError), error:
 
692
            logger.error(u"Unknown protocol version: %s", error)
 
693
            return
 
694
        
 
695
        # Note: gnutls.connection.X509Credentials is really a generic
 
696
        # GnuTLS certificate credentials object so long as no X.509
 
697
        # keys are added to it.  Therefore, we can use it here despite
 
698
        # using OpenPGP certificates.
 
699
        
 
700
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
701
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
702
        #                     "+DHE-DSS"))
 
703
        # Use a fallback default, since this MUST be set.
 
704
        priority = self.server.settings.get("priority", "NORMAL")
 
705
        (gnutls.library.functions
 
706
         .gnutls_priority_set_direct(session._c_object,
 
707
                                     priority, None))
 
708
        
 
709
        try:
 
710
            session.handshake()
 
711
        except gnutls.errors.GNUTLSError, error:
 
712
            logger.warning(u"Handshake failed: %s", error)
 
713
            # Do not run session.bye() here: the session is not
 
714
            # established.  Just abandon the request.
 
715
            return
 
716
        logger.debug(u"Handshake succeeded")
 
717
        try:
 
718
            fpr = fingerprint(peer_certificate(session))
 
719
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
720
            logger.warning(u"Bad certificate: %s", error)
 
721
            session.bye()
 
722
            return
 
723
        logger.debug(u"Fingerprint: %s", fpr)
 
724
        
 
725
        for c in self.server.clients:
 
726
            if c.fingerprint == fpr:
 
727
                client = c
 
728
                break
 
729
        else:
 
730
            logger.warning(u"Client not found for fingerprint: %s",
 
731
                           fpr)
 
732
            session.bye()
 
733
            return
 
734
        # Have to check if client.still_valid(), since it is possible
 
735
        # that the client timed out while establishing the GnuTLS
 
736
        # session.
 
737
        if not client.still_valid():
 
738
            logger.warning(u"Client %(name)s is invalid",
 
739
                           vars(client))
 
740
            session.bye()
 
741
            return
 
742
        ## This won't work here, since we're in a fork.
 
743
        # client.checked_ok()
 
744
        sent_size = 0
 
745
        while sent_size < len(client.secret):
 
746
            sent = session.send(client.secret[sent_size:])
 
747
            logger.debug(u"Sent: %d, remaining: %d",
 
748
                         sent, len(client.secret)
 
749
                         - (sent_size + sent))
 
750
            sent_size += sent
 
751
        session.bye()
 
752
 
 
753
 
 
754
class IPv6_TCPServer(SocketServer.ForkingMixIn,
795
755
                     SocketServer.TCPServer, object):
796
756
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
797
757
    Attributes:
856
816
            return super(IPv6_TCPServer, self).server_activate()
857
817
    def enable(self):
858
818
        self.enabled = True
859
 
    def handle_ipc(self, source, condition, file_objects={}):
860
 
        condition_names = {
861
 
            gobject.IO_IN: "IN", # There is data to read.
862
 
            gobject.IO_OUT: "OUT", # Data can be written (without
863
 
                                   # blocking).
864
 
            gobject.IO_PRI: "PRI", # There is urgent data to read.
865
 
            gobject.IO_ERR: "ERR", # Error condition.
866
 
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
867
 
                                   # broken, usually for pipes and
868
 
                                   # sockets).
869
 
            }
870
 
        conditions_string = ' | '.join(name
871
 
                                       for cond, name in
872
 
                                       condition_names.iteritems()
873
 
                                       if cond & condition)
874
 
        logger.debug("Handling IPC: FD = %d, condition = %s", source,
875
 
                     conditions_string)
876
 
        
877
 
        # Turn the pipe file descriptor into a Python file object
878
 
        if source not in file_objects:
879
 
            file_objects[source] = os.fdopen(source, "r", 1)
880
 
        
881
 
        # Read a line from the file object
882
 
        cmdline = file_objects[source].readline()
883
 
        if not cmdline:             # Empty line means end of file
884
 
            # close the IPC pipe
885
 
            file_objects[source].close()
886
 
            del file_objects[source]
887
 
            
888
 
            # Stop calling this function
889
 
            return False
890
 
        
891
 
        logger.debug("IPC command: %r\n" % cmdline)
892
 
        
893
 
        # Parse and act on command
894
 
        cmd, args = cmdline.split(None, 1)
895
 
        if cmd == "NOTFOUND":
896
 
            if self.settings["use_dbus"]:
897
 
                # Emit D-Bus signal
898
 
                mandos_dbus_service.ClientNotFound(args)
899
 
        elif cmd == "INVALID":
900
 
            if self.settings["use_dbus"]:
901
 
                for client in self.clients:
902
 
                    if client.name == args:
903
 
                        # Emit D-Bus signal
904
 
                        client.Rejected()
905
 
                        break
906
 
        elif cmd == "SENDING":
907
 
            for client in self.clients:
908
 
                if client.name == args:
909
 
                    client.checked_ok()
910
 
                    if self.settings["use_dbus"]:
911
 
                        # Emit D-Bus signal
912
 
                        client.ReceivedSecret()
913
 
                    break
914
 
        else:
915
 
            logger.error("Unknown IPC command: %r", cmdline)
916
 
        
917
 
        # Keep calling this function
918
 
        return True
919
819
 
920
820
 
921
821
def string_to_delta(interval):
1027
927
 
1028
928
 
1029
929
def main():
1030
 
    
1031
 
    ######################################################################
1032
 
    # Parsing of options, both command line and config file
1033
 
    
1034
930
    parser = optparse.OptionParser(version = "%%prog %s" % version)
1035
931
    parser.add_option("-i", "--interface", type="string",
1036
932
                      metavar="IF", help="Bind to interface IF")
1105
1001
    del options
1106
1002
    # Now we have our good server settings in "server_settings"
1107
1003
    
1108
 
    ##################################################################
1109
 
    
1110
1004
    # For convenience
1111
1005
    debug = server_settings["debug"]
1112
1006
    use_dbus = server_settings["use_dbus"]
1118
1012
    
1119
1013
    if server_settings["servicename"] != "Mandos":
1120
1014
        syslogger.setFormatter(logging.Formatter
1121
 
                               ('Mandos (%s) [%%(process)d]:'
1122
 
                                ' %%(levelname)s: %%(message)s'
 
1015
                               ('Mandos (%s): %%(levelname)s:'
 
1016
                                ' %%(message)s'
1123
1017
                                % server_settings["servicename"]))
1124
1018
    
1125
1019
    # Parse config file with clients
1131
1025
    client_config = ConfigParser.SafeConfigParser(client_defaults)
1132
1026
    client_config.read(os.path.join(server_settings["configdir"],
1133
1027
                                    "clients.conf"))
1134
 
 
1135
 
    global mandos_dbus_service
1136
 
    mandos_dbus_service = None
1137
1028
    
1138
1029
    clients = Set()
1139
1030
    tcp_server = IPv6_TCPServer((server_settings["address"],
1225
1116
        daemon()
1226
1117
    
1227
1118
    try:
1228
 
        with closing(pidfile):
1229
 
            pid = os.getpid()
1230
 
            pidfile.write(str(pid) + "\n")
 
1119
        pid = os.getpid()
 
1120
        pidfile.write(str(pid) + "\n")
 
1121
        pidfile.close()
1231
1122
        del pidfile
1232
1123
    except IOError:
1233
1124
        logger.error(u"Could not write to file %r with PID %d",
1259
1150
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1260
1151
    
1261
1152
    if use_dbus:
1262
 
        class MandosDBusService(dbus.service.Object):
 
1153
        class MandosServer(dbus.service.Object):
1263
1154
            """A D-Bus proxy object"""
1264
1155
            def __init__(self):
1265
1156
                dbus.service.Object.__init__(self, bus, "/")
1270
1161
                "D-Bus signal"
1271
1162
                pass
1272
1163
            
1273
 
            @dbus.service.signal(_interface, signature="s")
1274
 
            def ClientNotFound(self, fingerprint):
1275
 
                "D-Bus signal"
1276
 
                pass
1277
 
            
1278
1164
            @dbus.service.signal(_interface, signature="os")
1279
1165
            def ClientRemoved(self, objpath, name):
1280
1166
                "D-Bus signal"
1309
1195
            
1310
1196
            del _interface
1311
1197
        
1312
 
        mandos_dbus_service = MandosDBusService()
 
1198
        mandos_server = MandosServer()
1313
1199
    
1314
1200
    for client in clients:
1315
1201
        if use_dbus:
1316
1202
            # Emit D-Bus signal
1317
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1318
 
                                            client.GetAllProperties())
 
1203
            mandos_server.ClientAdded(client.dbus_object_path,
 
1204
                                      client.GetAllProperties())
1319
1205
        client.enable()
1320
1206
    
1321
1207
    tcp_server.enable()