/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-27 13:33:17 UTC
  • mfrom: (288.1.2 mandos-pipe-ipc)
  • Revision ID: teddy@fukt.bsnet.se-20090327133317-riwt5s5orrisozuj
Merge from pipe IPC branch.

* mandos (AvahiService.rename, main): Include PID in log messages when
                                      using a different service name.
  (Client.ReceivedSecret, Client.Rejected): New D-Bus signals.
  (TCP_handler.handle): Send IPC to parent process.
  (ForkingMixInWithPipe): New mixin class.
  (IPv6_TCPServer): Inherit from ForkingMixInWithPipe instead of
                    SocketServer.ForkingMixIn.
  (IPv6_TCPServer.handle_ipc): New method.
  (main/mandos_server): Renamed to "mandos_dbus_service" and made
                        global.
  (main/MandosServer): Renamed to "MandosDBusService".
  (main/MandosDBusService.ClientNotFound): New D-Bus signal.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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): %%(levelname)s:'
141
 
                                ' %%(message)s' % self.name))
 
140
                               ('Mandos (%s) [%%(process)d]:'
 
141
                                ' %%(levelname)s: %%(message)s'
 
142
                                % self.name))
142
143
        self.remove()
143
144
        self.add()
144
145
        self.rename_count += 1
542
543
        "D-Bus signal"
543
544
        pass
544
545
    
 
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
    
545
558
    # SetChecker - method
546
559
    @dbus.service.method(_interface, in_signature="s")
547
560
    def SetChecker(self, checker):
678
691
    def handle(self):
679
692
        logger.info(u"TCP connection from: %s",
680
693
                    unicode(self.client_address))
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,
 
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,
755
795
                     SocketServer.TCPServer, object):
756
796
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
757
797
    Attributes:
816
856
            return super(IPv6_TCPServer, self).server_activate()
817
857
    def enable(self):
818
858
        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
819
919
 
820
920
 
821
921
def string_to_delta(interval):
927
1027
 
928
1028
 
929
1029
def main():
 
1030
    
 
1031
    ######################################################################
 
1032
    # Parsing of options, both command line and config file
 
1033
    
930
1034
    parser = optparse.OptionParser(version = "%%prog %s" % version)
931
1035
    parser.add_option("-i", "--interface", type="string",
932
1036
                      metavar="IF", help="Bind to interface IF")
1001
1105
    del options
1002
1106
    # Now we have our good server settings in "server_settings"
1003
1107
    
 
1108
    ##################################################################
 
1109
    
1004
1110
    # For convenience
1005
1111
    debug = server_settings["debug"]
1006
1112
    use_dbus = server_settings["use_dbus"]
1012
1118
    
1013
1119
    if server_settings["servicename"] != "Mandos":
1014
1120
        syslogger.setFormatter(logging.Formatter
1015
 
                               ('Mandos (%s): %%(levelname)s:'
1016
 
                                ' %%(message)s'
 
1121
                               ('Mandos (%s) [%%(process)d]:'
 
1122
                                ' %%(levelname)s: %%(message)s'
1017
1123
                                % server_settings["servicename"]))
1018
1124
    
1019
1125
    # Parse config file with clients
1025
1131
    client_config = ConfigParser.SafeConfigParser(client_defaults)
1026
1132
    client_config.read(os.path.join(server_settings["configdir"],
1027
1133
                                    "clients.conf"))
 
1134
 
 
1135
    global mandos_dbus_service
 
1136
    mandos_dbus_service = None
1028
1137
    
1029
1138
    clients = Set()
1030
1139
    tcp_server = IPv6_TCPServer((server_settings["address"],
1116
1225
        daemon()
1117
1226
    
1118
1227
    try:
1119
 
        pid = os.getpid()
1120
 
        pidfile.write(str(pid) + "\n")
1121
 
        pidfile.close()
 
1228
        with closing(pidfile):
 
1229
            pid = os.getpid()
 
1230
            pidfile.write(str(pid) + "\n")
1122
1231
        del pidfile
1123
1232
    except IOError:
1124
1233
        logger.error(u"Could not write to file %r with PID %d",
1150
1259
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1151
1260
    
1152
1261
    if use_dbus:
1153
 
        class MandosServer(dbus.service.Object):
 
1262
        class MandosDBusService(dbus.service.Object):
1154
1263
            """A D-Bus proxy object"""
1155
1264
            def __init__(self):
1156
1265
                dbus.service.Object.__init__(self, bus, "/")
1161
1270
                "D-Bus signal"
1162
1271
                pass
1163
1272
            
 
1273
            @dbus.service.signal(_interface, signature="s")
 
1274
            def ClientNotFound(self, fingerprint):
 
1275
                "D-Bus signal"
 
1276
                pass
 
1277
            
1164
1278
            @dbus.service.signal(_interface, signature="os")
1165
1279
            def ClientRemoved(self, objpath, name):
1166
1280
                "D-Bus signal"
1195
1309
            
1196
1310
            del _interface
1197
1311
        
1198
 
        mandos_server = MandosServer()
 
1312
        mandos_dbus_service = MandosDBusService()
1199
1313
    
1200
1314
    for client in clients:
1201
1315
        if use_dbus:
1202
1316
            # Emit D-Bus signal
1203
 
            mandos_server.ClientAdded(client.dbus_object_path,
1204
 
                                      client.GetAllProperties())
 
1317
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
 
1318
                                            client.GetAllProperties())
1205
1319
        client.enable()
1206
1320
    
1207
1321
    tcp_server.enable()