/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: 2010-06-19 00:37:04 UTC
  • mto: (24.1.149 mandos)
  • mto: This revision was merged to the branch mainline in revision 417.
  • Revision ID: teddy@fukt.bsnet.se-20100619003704-vpicvssvv1ktg2om
* mandos (ClientHandler.handle): Set up the GnuTLS session object
                                 before reading the protocol number.
 (ClientHandler.handle/ProxyObject): New.

Show diffs side-by-side

added added

removed removed

Lines of Context:
55
55
import logging
56
56
import logging.handlers
57
57
import pwd
58
 
from contextlib import closing
 
58
import contextlib
59
59
import struct
60
60
import fcntl
61
61
import functools
 
62
import cPickle as pickle
 
63
import select
62
64
 
63
65
import dbus
64
66
import dbus.service
242
244
    enabled:    bool()
243
245
    last_checked_ok: datetime.datetime(); (UTC) or None
244
246
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
 
                                      until this client is invalid
 
247
                                      until this client is disabled
246
248
    interval:   datetime.timedelta(); How often to start a new checker
247
249
    disable_hook:  If set, called by disable() as disable_hook(self)
248
250
    checker:    subprocess.Popen(); a running checker process used
290
292
        if u"secret" in config:
291
293
            self.secret = config[u"secret"].decode(u"base64")
292
294
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])),
296
 
                              "rb")) as secfile:
 
295
            with open(os.path.expanduser(os.path.expandvars
 
296
                                         (config[u"secfile"])),
 
297
                      "rb") as secfile:
297
298
                self.secret = secfile.read()
298
299
        else:
299
300
            raise TypeError(u"No secret or secfile for client %s"
396
397
        # client would inevitably timeout, since no checker would get
397
398
        # a chance to run to completion.  If we instead leave running
398
399
        # checkers alone, the checker would have to take more time
399
 
        # than 'timeout' for the client to be declared invalid, which
400
 
        # is as it should be.
 
400
        # than 'timeout' for the client to be disabled, which is as it
 
401
        # should be.
401
402
        
402
403
        # If a checker exists, make sure it is not a zombie
403
404
        try:
475
476
            if error.errno != errno.ESRCH: # No such process
476
477
                raise
477
478
        self.checker = None
478
 
    
479
 
    def still_valid(self):
480
 
        """Has the timeout not yet passed for this client?"""
481
 
        if not getattr(self, u"enabled", False):
482
 
            return False
483
 
        now = datetime.datetime.utcnow()
484
 
        if self.last_checked_ok is None:
485
 
            return now < (self.created + self.timeout)
486
 
        else:
487
 
            return now < (self.last_checked_ok + self.timeout)
488
479
 
489
480
 
490
481
def dbus_service_property(dbus_interface, signature=u"v",
499
490
    dbus.service.method, except there is only "signature", since the
500
491
    type from Get() and the type sent to Set() is the same.
501
492
    """
 
493
    # Encoding deeply encoded byte arrays is not supported yet by the
 
494
    # "Set" method, so we fail early here:
 
495
    if byte_arrays and signature != u"ay":
 
496
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
497
                         u" signature %r" % signature)
502
498
    def decorator(func):
503
499
        func._dbus_is_property = True
504
500
        func._dbus_interface = dbus_interface
590
586
        if prop._dbus_access == u"read":
591
587
            raise DBusPropertyAccessException(property_name)
592
588
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
589
            # The byte_arrays option is not supported yet on
 
590
            # signatures other than "ay".
 
591
            if prop._dbus_signature != u"ay":
 
592
                raise ValueError
593
593
            value = dbus.ByteArray(''.join(unichr(byte)
594
594
                                           for byte in value))
595
595
        prop(value)
781
781
                                 dbus.Boolean(False, variant_level=1))
782
782
        return r
783
783
    
784
 
    ## D-Bus methods & signals
 
784
    ## D-Bus methods, signals & properties
785
785
    _interface = u"se.bsnet.fukt.Mandos.Client"
786
786
    
787
 
    # CheckedOK - method
788
 
    @dbus.service.method(_interface)
789
 
    def CheckedOK(self):
790
 
        return self.checked_ok()
 
787
    ## Signals
791
788
    
792
789
    # CheckerCompleted - signal
793
790
    @dbus.service.signal(_interface, signature=u"nxs")
819
816
        "D-Bus signal"
820
817
        pass
821
818
    
 
819
    ## Methods
 
820
    
 
821
    # CheckedOK - method
 
822
    @dbus.service.method(_interface)
 
823
    def CheckedOK(self):
 
824
        return self.checked_ok()
 
825
    
822
826
    # Enable - method
823
827
    @dbus.service.method(_interface)
824
828
    def Enable(self):
842
846
    def StopChecker(self):
843
847
        self.stop_checker()
844
848
    
 
849
    ## Properties
 
850
    
845
851
    # name - property
846
852
    @dbus_service_property(_interface, signature=u"s", access=u"read")
847
853
    def name_dbus_property(self):
990
996
    def handle(self):
991
997
        logger.info(u"TCP connection from: %s",
992
998
                    unicode(self.client_address))
993
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
 
999
        logger.debug(u"IPC Pipe FD: %d",
 
1000
                     self.server.child_pipe[1].fileno())
994
1001
        # Open IPC pipe to parent process
995
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
1002
        with contextlib.nested(self.server.child_pipe[1],
 
1003
                               self.server.parent_pipe[0]
 
1004
                               ) as (ipc, ipc_return):
996
1005
            session = (gnutls.connection
997
1006
                       .ClientSession(self.request,
998
1007
                                      gnutls.connection
999
1008
                                      .X509Credentials()))
1000
1009
            
1001
 
            line = self.request.makefile().readline()
1002
 
            logger.debug(u"Protocol version: %r", line)
1003
 
            try:
1004
 
                if int(line.strip().split()[0]) > 1:
1005
 
                    raise RuntimeError
1006
 
            except (ValueError, IndexError, RuntimeError), error:
1007
 
                logger.error(u"Unknown protocol version: %s", error)
1008
 
                return
1009
 
            
1010
1010
            # Note: gnutls.connection.X509Credentials is really a
1011
1011
            # generic GnuTLS certificate credentials object so long as
1012
1012
            # no X.509 keys are added to it.  Therefore, we can use it
1024
1024
             .gnutls_priority_set_direct(session._c_object,
1025
1025
                                         priority, None))
1026
1026
            
 
1027
            # Start communication using the Mandos protocol
 
1028
            # Get protocol number
 
1029
            line = self.request.makefile().readline()
 
1030
            logger.debug(u"Protocol version: %r", line)
 
1031
            try:
 
1032
                if int(line.strip().split()[0]) > 1:
 
1033
                    raise RuntimeError
 
1034
            except (ValueError, IndexError, RuntimeError), error:
 
1035
                logger.error(u"Unknown protocol version: %s", error)
 
1036
                return
 
1037
            
 
1038
            # Start GnuTLS connection
1027
1039
            try:
1028
1040
                session.handshake()
1029
1041
            except gnutls.errors.GNUTLSError, error:
1033
1045
                return
1034
1046
            logger.debug(u"Handshake succeeded")
1035
1047
            try:
1036
 
                fpr = self.fingerprint(self.peer_certificate(session))
1037
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
1038
 
                logger.warning(u"Bad certificate: %s", error)
1039
 
                session.bye()
1040
 
                return
1041
 
            logger.debug(u"Fingerprint: %s", fpr)
1042
 
            
1043
 
            for c in self.server.clients:
1044
 
                if c.fingerprint == fpr:
1045
 
                    client = c
1046
 
                    break
1047
 
            else:
1048
 
                ipc.write(u"NOTFOUND %s %s\n"
1049
 
                          % (fpr, unicode(self.client_address)))
1050
 
                session.bye()
1051
 
                return
1052
 
            # Have to check if client.still_valid(), since it is
1053
 
            # possible that the client timed out while establishing
1054
 
            # the GnuTLS session.
1055
 
            if not client.still_valid():
1056
 
                ipc.write(u"INVALID %s\n" % client.name)
1057
 
                session.bye()
1058
 
                return
1059
 
            ipc.write(u"SENDING %s\n" % client.name)
1060
 
            sent_size = 0
1061
 
            while sent_size < len(client.secret):
1062
 
                sent = session.send(client.secret[sent_size:])
1063
 
                logger.debug(u"Sent: %d, remaining: %d",
1064
 
                             sent, len(client.secret)
1065
 
                             - (sent_size + sent))
1066
 
                sent_size += sent
1067
 
            session.bye()
 
1048
                try:
 
1049
                    fpr = self.fingerprint(self.peer_certificate
 
1050
                                           (session))
 
1051
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1052
                    logger.warning(u"Bad certificate: %s", error)
 
1053
                    return
 
1054
                logger.debug(u"Fingerprint: %s", fpr)
 
1055
 
 
1056
                for c in self.server.clients:
 
1057
                    if c.fingerprint == fpr:
 
1058
                        client = c
 
1059
                        break
 
1060
                else:
 
1061
                    ipc.write(u"NOTFOUND %s %s\n"
 
1062
                              % (fpr, unicode(self.client_address)))
 
1063
                    return
 
1064
                
 
1065
                class ClientProxy(object):
 
1066
                    """Client proxy object.  Not for calling methods."""
 
1067
                    def __init__(self, client):
 
1068
                        self.client = client
 
1069
                    def __getattr__(self, name):
 
1070
                        if name.startswith("ipc_"):
 
1071
                            def tempfunc():
 
1072
                                ipc.write("%s %s\n" % (name[4:].upper(),
 
1073
                                                       self.client.name))
 
1074
                            return tempfunc
 
1075
                        if not hasattr(self.client, name):
 
1076
                            raise AttributeError
 
1077
                        ipc.write(u"GETATTR %s %s\n"
 
1078
                                  % (name, self.client.fingerprint))
 
1079
                        return pickle.load(ipc_return)
 
1080
                clientproxy = ClientProxy(client)
 
1081
                # Have to check if client.enabled, since it is
 
1082
                # possible that the client was disabled since the
 
1083
                # GnuTLS session was established.
 
1084
                if not clientproxy.enabled:
 
1085
                    clientproxy.ipc_disabled()
 
1086
                    return
 
1087
                
 
1088
                clientproxy.ipc_sending()
 
1089
                sent_size = 0
 
1090
                while sent_size < len(client.secret):
 
1091
                    sent = session.send(client.secret[sent_size:])
 
1092
                    logger.debug(u"Sent: %d, remaining: %d",
 
1093
                                 sent, len(client.secret)
 
1094
                                 - (sent_size + sent))
 
1095
                    sent_size += sent
 
1096
            finally:
 
1097
                session.bye()
1068
1098
    
1069
1099
    @staticmethod
1070
1100
    def peer_certificate(session):
1130
1160
        return hex_fpr
1131
1161
 
1132
1162
 
1133
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1134
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1163
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
 
1164
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1135
1165
    def process_request(self, request, client_address):
1136
1166
        """Overrides and wraps the original process_request().
1137
1167
        
1138
1168
        This function creates a new pipe in self.pipe
1139
1169
        """
1140
 
        self.pipe = os.pipe()
1141
 
        super(ForkingMixInWithPipe,
 
1170
        # Child writes to child_pipe
 
1171
        self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
 
1172
        # Parent writes to parent_pipe
 
1173
        self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
 
1174
        super(ForkingMixInWithPipes,
1142
1175
              self).process_request(request, client_address)
1143
 
        os.close(self.pipe[1])  # close write end
1144
 
        self.add_pipe(self.pipe[0])
1145
 
    def add_pipe(self, pipe):
 
1176
        # Close unused ends for parent
 
1177
        self.parent_pipe[0].close() # close read end
 
1178
        self.child_pipe[1].close()  # close write end
 
1179
        self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
 
1180
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1146
1181
        """Dummy function; override as necessary"""
1147
 
        os.close(pipe)
1148
 
 
1149
 
 
1150
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1182
        child_pipe_fd.close()
 
1183
        parent_pipe_fd.close()
 
1184
 
 
1185
 
 
1186
class IPv6_TCPServer(ForkingMixInWithPipes,
1151
1187
                     socketserver.TCPServer, object):
1152
1188
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1153
1189
    
1238
1274
            return socketserver.TCPServer.server_activate(self)
1239
1275
    def enable(self):
1240
1276
        self.enabled = True
1241
 
    def add_pipe(self, pipe):
 
1277
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1242
1278
        # Call "handle_ipc" for both data and EOF events
1243
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1244
 
                             self.handle_ipc)
1245
 
    def handle_ipc(self, source, condition, file_objects={}):
 
1279
        gobject.io_add_watch(child_pipe_fd.fileno(),
 
1280
                             gobject.IO_IN | gobject.IO_HUP,
 
1281
                             functools.partial(self.handle_ipc,
 
1282
                                               reply = parent_pipe_fd,
 
1283
                                               sender= child_pipe_fd))
 
1284
    def handle_ipc(self, source, condition, reply=None, sender=None):
1246
1285
        condition_names = {
1247
1286
            gobject.IO_IN: u"IN",   # There is data to read.
1248
1287
            gobject.IO_OUT: u"OUT", # Data can be written (without
1260
1299
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1261
1300
                     conditions_string)
1262
1301
        
1263
 
        # Turn the pipe file descriptor into a Python file object
1264
 
        if source not in file_objects:
1265
 
            file_objects[source] = os.fdopen(source, u"r", 1)
1266
 
        
1267
1302
        # Read a line from the file object
1268
 
        cmdline = file_objects[source].readline()
 
1303
        cmdline = sender.readline()
1269
1304
        if not cmdline:             # Empty line means end of file
1270
 
            # close the IPC pipe
1271
 
            file_objects[source].close()
1272
 
            del file_objects[source]
 
1305
            # close the IPC pipes
 
1306
            sender.close()
 
1307
            reply.close()
1273
1308
            
1274
1309
            # Stop calling this function
1275
1310
            return False
1280
1315
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1281
1316
        
1282
1317
        if cmd == u"NOTFOUND":
1283
 
            logger.warning(u"Client not found for fingerprint: %s",
1284
 
                           args)
 
1318
            fpr, address = args.split(None, 1)
 
1319
            logger.warning(u"Client not found for fingerprint: %s, ad"
 
1320
                           u"dress: %s", fpr, address)
1285
1321
            if self.use_dbus:
1286
1322
                # Emit D-Bus signal
1287
 
                mandos_dbus_service.ClientNotFound(args)
1288
 
        elif cmd == u"INVALID":
 
1323
                mandos_dbus_service.ClientNotFound(fpr, address)
 
1324
        elif cmd == u"DISABLED":
1289
1325
            for client in self.clients:
1290
1326
                if client.name == args:
1291
 
                    logger.warning(u"Client %s is invalid", args)
 
1327
                    logger.warning(u"Client %s is disabled", args)
1292
1328
                    if self.use_dbus:
1293
1329
                        # Emit D-Bus signal
1294
1330
                        client.Rejected()
1295
1331
                    break
1296
1332
            else:
1297
 
                logger.error(u"Unknown client %s is invalid", args)
 
1333
                logger.error(u"Unknown client %s is disabled", args)
1298
1334
        elif cmd == u"SENDING":
1299
1335
            for client in self.clients:
1300
1336
                if client.name == args:
1307
1343
            else:
1308
1344
                logger.error(u"Sending secret to unknown client %s",
1309
1345
                             args)
 
1346
        elif cmd == u"GETATTR":
 
1347
            attr_name, fpr = args.split(None, 1)
 
1348
            for client in self.clients:
 
1349
                if client.fingerprint == fpr:
 
1350
                    attr_value = getattr(client, attr_name, None)
 
1351
                    logger.debug("IPC reply: %r", attr_value)
 
1352
                    pickle.dump(attr_value, reply)
 
1353
                    break
 
1354
            else:
 
1355
                logger.error(u"Client %s on address %s requesting "
 
1356
                             u"attribute %s not found", fpr, address,
 
1357
                             attr_name)
 
1358
                pickle.dump(None, reply)
1310
1359
        else:
1311
1360
            logger.error(u"Unknown IPC command: %r", cmdline)
1312
1361
        
1367
1416
        def if_nametoindex(interface):
1368
1417
            "Get an interface index the hard way, i.e. using fcntl()"
1369
1418
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1370
 
            with closing(socket.socket()) as s:
 
1419
            with contextlib.closing(socket.socket()) as s:
1371
1420
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1372
1421
                                    struct.pack(str(u"16s16x"),
1373
1422
                                                interface))
1606
1655
        daemon()
1607
1656
    
1608
1657
    try:
1609
 
        with closing(pidfile):
 
1658
        with pidfile:
1610
1659
            pid = os.getpid()
1611
1660
            pidfile.write(str(pid) + "\n")
1612
1661
        del pidfile
1630
1679
                dbus.service.Object.__init__(self, bus, u"/")
1631
1680
            _interface = u"se.bsnet.fukt.Mandos"
1632
1681
            
1633
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1634
 
            def ClientAdded(self, objpath, properties):
 
1682
            @dbus.service.signal(_interface, signature=u"o")
 
1683
            def ClientAdded(self, objpath):
1635
1684
                "D-Bus signal"
1636
1685
                pass
1637
1686
            
1638
 
            @dbus.service.signal(_interface, signature=u"s")
1639
 
            def ClientNotFound(self, fingerprint):
 
1687
            @dbus.service.signal(_interface, signature=u"ss")
 
1688
            def ClientNotFound(self, fingerprint, address):
1640
1689
                "D-Bus signal"
1641
1690
                pass
1642
1691
            
1699
1748
    for client in tcp_server.clients:
1700
1749
        if use_dbus:
1701
1750
            # Emit D-Bus signal
1702
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1703
 
                                            client.GetAll(u""))
 
1751
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1704
1752
        client.enable()
1705
1753
    
1706
1754
    tcp_server.enable()