/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: Teddy Hogeborn
  • Date: 2009-01-05 23:26:06 UTC
  • mfrom: (237.2.11 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20090105232606-2iohqjcsfj076z7i
Merge from trunk, but disable the unfinished D-Bus feature:

* Makefile (PROGS): Removed "mandos-list".
  (mandos-list): Removed.
* mandos (main): Hide "--no-dbus" option.  Hard-code "use_dbus" to
                 "False".
* mandos-list: Removed.
* mandos.xml (SYNOPSIS): Removed "--no-dbus" option.
  (OPTIONS): - '' -
  (D-BUS INTERFACE): Removed section.

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.2"
70
70
 
71
71
logger = logging.Logger('mandos')
72
72
syslogger = (logging.handlers.SysLogHandler
178
178
class Client(dbus.service.Object):
179
179
    """A representation of a client host served by this server.
180
180
    Attributes:
181
 
    name:       string; from the config file, used in log messages and
182
 
                        D-Bus identifiers
 
181
    name:       string; from the config file, used in log messages
183
182
    fingerprint: string (40 or 32 hexadecimal digits); used to
184
183
                 uniquely identify the client
185
184
    secret:     bytestring; sent verbatim (over TLS) to client
226
225
        if config is None:
227
226
            config = {}
228
227
        logger.debug(u"Creating client %r", self.name)
229
 
        self.use_dbus = False   # During __init__
 
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)
230
235
        # Uppercase and remove spaces from fingerprint for later
231
236
        # comparison purposes with return value from the fingerprint()
232
237
        # function
256
261
        self.disable_initiator_tag = None
257
262
        self.checker_callback_tag = None
258
263
        self.checker_command = config["checker"]
259
 
        self.last_connect = None
260
 
        # Only now, when this client is initialized, can it show up on
261
 
        # the D-Bus
262
 
        self.use_dbus = use_dbus
263
 
        if self.use_dbus:
264
 
            self.dbus_object_path = (dbus.ObjectPath
265
 
                                     ("/clients/"
266
 
                                      + self.name.replace(".", "_")))
267
 
            dbus.service.Object.__init__(self, bus,
268
 
                                         self.dbus_object_path)
269
264
    
270
265
    def enable(self):
271
266
        """Start this client's checker and timeout hooks"""
324
319
            # Emit D-Bus signal
325
320
            self.PropertyChanged(dbus.String(u"checker_running"),
326
321
                                 dbus.Boolean(False, variant_level=1))
327
 
        if os.WIFEXITED(condition):
328
 
            exitstatus = os.WEXITSTATUS(condition)
329
 
            if exitstatus == 0:
330
 
                logger.info(u"Checker for %(name)s succeeded",
331
 
                            vars(self))
332
 
                self.checked_ok()
333
 
            else:
334
 
                logger.info(u"Checker for %(name)s failed",
335
 
                            vars(self))
 
322
        if (os.WIFEXITED(condition)
 
323
            and (os.WEXITSTATUS(condition) == 0)):
 
324
            logger.info(u"Checker for %(name)s succeeded",
 
325
                        vars(self))
336
326
            if self.use_dbus:
337
327
                # Emit D-Bus signal
338
 
                self.CheckerCompleted(dbus.Int16(exitstatus),
339
 
                                      dbus.Int64(condition),
 
328
                self.CheckerCompleted(dbus.Boolean(True),
 
329
                                      dbus.UInt16(condition),
340
330
                                      dbus.String(command))
341
 
        else:
 
331
            self.bump_timeout()
 
332
        elif not os.WIFEXITED(condition):
342
333
            logger.warning(u"Checker for %(name)s crashed?",
343
334
                           vars(self))
344
335
            if self.use_dbus:
345
336
                # Emit D-Bus signal
346
 
                self.CheckerCompleted(dbus.Int16(-1),
347
 
                                      dbus.Int64(condition),
 
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),
348
347
                                      dbus.String(command))
349
348
    
350
 
    def checked_ok(self):
 
349
    def bump_timeout(self):
351
350
        """Bump up the timeout for this client.
352
351
        This should only be called when the client has been seen,
353
352
        alive and well.
449
448
            return now < (self.last_checked_ok + self.timeout)
450
449
    
451
450
    ## D-Bus methods & signals
452
 
    _interface = u"se.bsnet.fukt.Mandos.Client"
 
451
    _interface = u"org.mandos_system.Mandos.Client"
453
452
    
454
 
    # CheckedOK - method
455
 
    CheckedOK = dbus.service.method(_interface)(checked_ok)
456
 
    CheckedOK.__name__ = "CheckedOK"
 
453
    # BumpTimeout - method
 
454
    BumpTimeout = dbus.service.method(_interface)(bump_timeout)
 
455
    BumpTimeout.__name__ = "BumpTimeout"
457
456
    
458
457
    # CheckerCompleted - signal
459
 
    @dbus.service.signal(_interface, signature="nxs")
460
 
    def CheckerCompleted(self, exitcode, waitstatus, command):
 
458
    @dbus.service.signal(_interface, signature="bqs")
 
459
    def CheckerCompleted(self, success, condition, command):
461
460
        "D-Bus signal"
462
461
        pass
463
462
    
504
503
                dbus.String("checker_running"):
505
504
                    dbus.Boolean(self.checker is not None,
506
505
                                 variant_level=1),
507
 
                dbus.String("object_path"):
508
 
                    dbus.ObjectPath(self.dbus_object_path,
509
 
                                    variant_level=1)
510
506
                }, signature="sv")
511
507
    
512
508
    # IsStillValid - method
595
591
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
596
592
        # ...do the normal thing
597
593
        return session.peer_certificate
598
 
    list_size = ctypes.c_uint(1)
 
594
    list_size = ctypes.c_uint()
599
595
    cert_list = (gnutls.library.functions
600
596
                 .gnutls_certificate_get_peers
601
597
                 (session._c_object, ctypes.byref(list_size)))
602
 
    if not bool(cert_list) and list_size.value != 0:
603
 
        raise gnutls.errors.GNUTLSError("error getting peer"
604
 
                                        " certificate")
605
598
    if list_size.value == 0:
606
599
        return None
607
600
    cert = cert_list[0]
656
649
    def handle(self):
657
650
        logger.info(u"TCP connection from: %s",
658
651
                    unicode(self.client_address))
659
 
        logger.debug(u"Pipe: %d", self.server.pipe[1])
660
 
        # Open IPC pipe to parent process
661
 
        with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
662
 
            session = (gnutls.connection
663
 
                       .ClientSession(self.request,
664
 
                                      gnutls.connection
665
 
                                      .X509Credentials()))
666
 
            
667
 
            line = self.request.makefile().readline()
668
 
            logger.debug(u"Protocol version: %r", line)
669
 
            try:
670
 
                if int(line.strip().split()[0]) > 1:
671
 
                    raise RuntimeError
672
 
            except (ValueError, IndexError, RuntimeError), error:
673
 
                logger.error(u"Unknown protocol version: %s", error)
674
 
                return
675
 
            
676
 
            # Note: gnutls.connection.X509Credentials is really a
677
 
            # generic GnuTLS certificate credentials object so long as
678
 
            # no X.509 keys are added to it.  Therefore, we can use it
679
 
            # here despite using OpenPGP certificates.
680
 
            
681
 
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
682
 
            #                     "+AES-256-CBC", "+SHA1",
683
 
            #                     "+COMP-NULL", "+CTYPE-OPENPGP",
684
 
            #                     "+DHE-DSS"))
685
 
            # Use a fallback default, since this MUST be set.
686
 
            priority = self.server.settings.get("priority", "NORMAL")
687
 
            (gnutls.library.functions
688
 
             .gnutls_priority_set_direct(session._c_object,
689
 
                                         priority, None))
690
 
 
691
 
            try:
692
 
                session.handshake()
693
 
            except gnutls.errors.GNUTLSError, error:
694
 
                logger.warning(u"Handshake failed: %s", error)
695
 
                # Do not run session.bye() here: the session is not
696
 
                # established.  Just abandon the request.
697
 
                return
698
 
            logger.debug(u"Handshake succeeded")
699
 
            try:
700
 
                fpr = fingerprint(peer_certificate(session))
701
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
702
 
                logger.warning(u"Bad certificate: %s", error)
703
 
                session.bye()
704
 
                return
705
 
            logger.debug(u"Fingerprint: %s", fpr)
706
 
            for c in self.server.clients:
707
 
                if c.fingerprint == fpr:
708
 
                    client = c
709
 
                    break
710
 
            else:
711
 
                logger.warning(u"Client not found for fingerprint: %s",
712
 
                               fpr)
713
 
                ipc.write("NOTFOUND %s\n" % fpr)
714
 
                session.bye()
715
 
                return
716
 
            # Have to check if client.still_valid(), since it is
717
 
            # possible that the client timed out while establishing
718
 
            # the GnuTLS session.
719
 
            if not client.still_valid():
720
 
                logger.warning(u"Client %(name)s is invalid",
721
 
                               vars(client))
722
 
                ipc.write("INVALID %s\n" % client.name)
723
 
                session.bye()
724
 
                return
725
 
            ipc.write("SENDING %s\n" % client.name)
726
 
            ## This won't work here, since we're in a fork.
727
 
            # client.checked_ok()
728
 
            sent_size = 0
729
 
            while sent_size < len(client.secret):
730
 
                sent = session.send(client.secret[sent_size:])
731
 
                logger.debug(u"Sent: %d, remaining: %d",
732
 
                             sent, len(client.secret)
733
 
                             - (sent_size + sent))
734
 
                sent_size += sent
735
 
            session.bye()
736
 
 
737
 
 
738
 
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
739
 
    """Like SocketServer.ForkingMixIn, but also pass a pipe.
740
 
    Assumes a gobject.MainLoop event loop.
741
 
    """
742
 
    def process_request(self, request, client_address):
743
 
        """This overrides and wraps the original process_request().
744
 
        This function creates a new pipe in self.pipe 
745
 
        """
746
 
        self.pipe = os.pipe()
747
 
        super(ForkingMixInWithPipe,
748
 
              self).process_request(request, client_address)
749
 
        os.close(self.pipe[1])  # close write end
750
 
        # Call "handle_ipc" for both data and EOF events
751
 
        gobject.io_add_watch(self.pipe[0],
752
 
                             gobject.IO_IN | gobject.IO_HUP,
753
 
                             self.handle_ipc)
754
 
    def handle_ipc(source, condition):
755
 
        """Dummy function; override as necessary"""
756
 
        os.close(source)
757
 
        return False
758
 
 
759
 
 
760
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
652
        session = (gnutls.connection
 
653
                   .ClientSession(self.request,
 
654
                                  gnutls.connection
 
655
                                  .X509Credentials()))
 
656
        
 
657
        line = self.request.makefile().readline()
 
658
        logger.debug(u"Protocol version: %r", line)
 
659
        try:
 
660
            if int(line.strip().split()[0]) > 1:
 
661
                raise RuntimeError
 
662
        except (ValueError, IndexError, RuntimeError), error:
 
663
            logger.error(u"Unknown protocol version: %s", error)
 
664
            return
 
665
        
 
666
        # Note: gnutls.connection.X509Credentials is really a generic
 
667
        # GnuTLS certificate credentials object so long as no X.509
 
668
        # keys are added to it.  Therefore, we can use it here despite
 
669
        # using OpenPGP certificates.
 
670
        
 
671
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
672
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
673
        #                "+DHE-DSS"))
 
674
        # Use a fallback default, since this MUST be set.
 
675
        priority = self.server.settings.get("priority", "NORMAL")
 
676
        (gnutls.library.functions
 
677
         .gnutls_priority_set_direct(session._c_object,
 
678
                                     priority, None))
 
679
        
 
680
        try:
 
681
            session.handshake()
 
682
        except gnutls.errors.GNUTLSError, error:
 
683
            logger.warning(u"Handshake failed: %s", error)
 
684
            # Do not run session.bye() here: the session is not
 
685
            # established.  Just abandon the request.
 
686
            return
 
687
        try:
 
688
            fpr = fingerprint(peer_certificate(session))
 
689
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
690
            logger.warning(u"Bad certificate: %s", error)
 
691
            session.bye()
 
692
            return
 
693
        logger.debug(u"Fingerprint: %s", fpr)
 
694
        for c in self.server.clients:
 
695
            if c.fingerprint == fpr:
 
696
                client = c
 
697
                break
 
698
        else:
 
699
            logger.warning(u"Client not found for fingerprint: %s",
 
700
                           fpr)
 
701
            session.bye()
 
702
            return
 
703
        # Have to check if client.still_valid(), since it is possible
 
704
        # that the client timed out while establishing the GnuTLS
 
705
        # session.
 
706
        if not client.still_valid():
 
707
            logger.warning(u"Client %(name)s is invalid",
 
708
                           vars(client))
 
709
            session.bye()
 
710
            return
 
711
        ## This won't work here, since we're in a fork.
 
712
        # client.bump_timeout()
 
713
        sent_size = 0
 
714
        while sent_size < len(client.secret):
 
715
            sent = session.send(client.secret[sent_size:])
 
716
            logger.debug(u"Sent: %d, remaining: %d",
 
717
                         sent, len(client.secret)
 
718
                         - (sent_size + sent))
 
719
            sent_size += sent
 
720
        session.bye()
 
721
 
 
722
 
 
723
class IPv6_TCPServer(SocketServer.ForkingMixIn,
761
724
                     SocketServer.TCPServer, object):
762
725
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
763
726
    Attributes:
815
778
            return super(IPv6_TCPServer, self).server_activate()
816
779
    def enable(self):
817
780
        self.enabled = True
818
 
    def handle_ipc(self, source, condition, file_objects={}):
819
 
        logger.debug("Handling IPC: %r : %r", source, condition)
820
 
        
821
 
        # Turn a file descriptor into a Python file object
822
 
        if source not in file_objects:
823
 
            file_objects[source] = os.fdopen(source, "r", 1)
824
 
        
825
 
        # Read a line from the file object
826
 
        cmdline = file_objects[source].readline()
827
 
        if not cmdline:             # Empty line means end of file
828
 
            # close the IPC pipe
829
 
            logger.debug("Closing: %r", source)
830
 
            file_objects[source].close()
831
 
            del file_objects[source]
832
 
 
833
 
            # Stop calling this function
834
 
            return False
835
 
        
836
 
        logger.debug("IPC command: %r\n" % cmdline)
837
 
        
838
 
        # Parse and act on command
839
 
        cmd, args = cmdline.split(None, 1)
840
 
        if cmd == "NOTFOUND":
841
 
            pass                # xxx
842
 
        elif cmd == "INVALID":
843
 
            pass                # xxx
844
 
        elif cmd == "SENDING":
845
 
            pass                # xxx
846
 
        else:
847
 
            logger.error("Unknown IPC command: %r", cmdline)
848
 
        
849
 
        # Keep calling this function
850
 
        return True
851
781
 
852
782
 
853
783
def string_to_delta(interval):
981
911
                      " files")
982
912
    parser.add_option("--no-dbus", action="store_false",
983
913
                      dest="use_dbus",
984
 
                      help="Do not provide D-Bus system bus"
985
 
                      " interface")
 
914
                      help=optparse.SUPPRESS_HELP)
986
915
    options = parser.parse_args()[0]
987
916
    
988
917
    if options.check:
1007
936
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
1008
937
    # Convert the SafeConfigParser object to a dict
1009
938
    server_settings = server_config.defaults()
1010
 
    # Use the appropriate methods on the non-string config options
1011
 
    server_settings["debug"] = server_config.getboolean("DEFAULT",
1012
 
                                                        "debug")
1013
 
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1014
 
                                                           "use_dbus")
1015
 
    if server_settings["port"]:
1016
 
        server_settings["port"] = server_config.getint("DEFAULT",
1017
 
                                                       "port")
 
939
    # Use getboolean on the boolean config options
 
940
    server_settings["debug"] = (server_config.getboolean
 
941
                                ("DEFAULT", "debug"))
 
942
    server_settings["use_dbus"] = (server_config.getboolean
 
943
                                   ("DEFAULT", "use_dbus"))
1018
944
    del server_config
1019
945
    
1020
946
    # Override the settings from the config file with command line
1031
957
    # For convenience
1032
958
    debug = server_settings["debug"]
1033
959
    use_dbus = server_settings["use_dbus"]
 
960
    use_dbus = False
1034
961
    
1035
962
    if not debug:
1036
963
        syslogger.setLevel(logging.WARNING)
1045
972
    # Parse config file with clients
1046
973
    client_defaults = { "timeout": "1h",
1047
974
                        "interval": "5m",
1048
 
                        "checker": "fping -q -- %%(host)s",
 
975
                        "checker": "fping -q -- %(host)s",
1049
976
                        "host": "",
1050
977
                        }
1051
978
    client_config = ConfigParser.SafeConfigParser(client_defaults)
1066
993
    
1067
994
    try:
1068
995
        uid = pwd.getpwnam("_mandos").pw_uid
 
996
    except KeyError:
 
997
        try:
 
998
            uid = pwd.getpwnam("mandos").pw_uid
 
999
        except KeyError:
 
1000
            try:
 
1001
                uid = pwd.getpwnam("nobody").pw_uid
 
1002
            except KeyError:
 
1003
                uid = 65534
 
1004
    try:
1069
1005
        gid = pwd.getpwnam("_mandos").pw_gid
1070
1006
    except KeyError:
1071
1007
        try:
1072
 
            uid = pwd.getpwnam("mandos").pw_uid
1073
1008
            gid = pwd.getpwnam("mandos").pw_gid
1074
1009
        except KeyError:
1075
1010
            try:
1076
 
                uid = pwd.getpwnam("nobody").pw_uid
1077
1011
                gid = pwd.getpwnam("nogroup").pw_gid
1078
1012
            except KeyError:
1079
 
                uid = 65534
1080
1013
                gid = 65534
1081
1014
    try:
1082
1015
        os.setuid(uid)
1104
1037
                            avahi.DBUS_INTERFACE_SERVER)
1105
1038
    # End of Avahi example code
1106
1039
    if use_dbus:
1107
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1040
        bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
 
1041
                                        bus)
1108
1042
    
1109
1043
    clients.update(Set(Client(name = section,
1110
1044
                              config
1164
1098
        class MandosServer(dbus.service.Object):
1165
1099
            """A D-Bus proxy object"""
1166
1100
            def __init__(self):
1167
 
                dbus.service.Object.__init__(self, bus, "/")
1168
 
            _interface = u"se.bsnet.fukt.Mandos"
1169
 
            
 
1101
                dbus.service.Object.__init__(self, bus,
 
1102
                                             "/Mandos")
 
1103
            _interface = u"org.mandos_system.Mandos"
 
1104
 
1170
1105
            @dbus.service.signal(_interface, signature="oa{sv}")
1171
1106
            def ClientAdded(self, objpath, properties):
1172
1107
                "D-Bus signal"
1173
1108
                pass
1174
 
            
1175
 
            @dbus.service.signal(_interface, signature="os")
1176
 
            def ClientRemoved(self, objpath, name):
 
1109
 
 
1110
            @dbus.service.signal(_interface, signature="o")
 
1111
            def ClientRemoved(self, objpath):
1177
1112
                "D-Bus signal"
1178
1113
                pass
1179
 
            
 
1114
 
1180
1115
            @dbus.service.method(_interface, out_signature="ao")
1181
1116
            def GetAllClients(self):
1182
 
                "D-Bus method"
1183
1117
                return dbus.Array(c.dbus_object_path for c in clients)
1184
 
            
 
1118
 
1185
1119
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1186
1120
            def GetAllClientsWithProperties(self):
1187
 
                "D-Bus method"
1188
1121
                return dbus.Dictionary(
1189
1122
                    ((c.dbus_object_path, c.GetAllProperties())
1190
1123
                     for c in clients),
1191
1124
                    signature="oa{sv}")
1192
 
            
 
1125
 
1193
1126
            @dbus.service.method(_interface, in_signature="o")
1194
1127
            def RemoveClient(self, object_path):
1195
 
                "D-Bus method"
1196
1128
                for c in clients:
1197
1129
                    if c.dbus_object_path == object_path:
1198
1130
                        clients.remove(c)
1200
1132
                        c.use_dbus = False
1201
1133
                        c.disable()
1202
1134
                        # Emit D-Bus signal
1203
 
                        self.ClientRemoved(object_path, c.name)
 
1135
                        self.ClientRemoved(object_path)
1204
1136
                        return
1205
1137
                raise KeyError
1206
 
            
 
1138
            @dbus.service.method(_interface)
 
1139
            def Quit(self):
 
1140
                main_loop.quit()
 
1141
 
1207
1142
            del _interface
1208
 
        
 
1143
    
1209
1144
        mandos_server = MandosServer()
1210
1145
    
1211
1146
    for client in clients: