/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-11-19 18:31:28 UTC
  • Revision ID: teddy@fukt.bsnet.se-20091119183128-ttstewh61xmtnil1
* Makefile (LINK_FORTIFY_LD): Bug fix: removed "-fPIE".
* mandos-keygen: Bug fix: Fix quoting for the "--password" option.

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
 
import contextlib
 
58
from contextlib import closing
59
59
import struct
60
60
import fcntl
61
61
import functools
62
 
import cPickle as pickle
63
62
 
64
63
import dbus
65
64
import dbus.service
243
242
    enabled:    bool()
244
243
    last_checked_ok: datetime.datetime(); (UTC) or None
245
244
    timeout:    datetime.timedelta(); How long from last_checked_ok
246
 
                                      until this client is disabled
 
245
                                      until this client is invalid
247
246
    interval:   datetime.timedelta(); How often to start a new checker
248
247
    disable_hook:  If set, called by disable() as disable_hook(self)
249
248
    checker:    subprocess.Popen(); a running checker process used
291
290
        if u"secret" in config:
292
291
            self.secret = config[u"secret"].decode(u"base64")
293
292
        elif u"secfile" in config:
294
 
            with open(os.path.expanduser(os.path.expandvars
295
 
                                         (config[u"secfile"])),
296
 
                      "rb") as secfile:
 
293
            with closing(open(os.path.expanduser
 
294
                              (os.path.expandvars
 
295
                               (config[u"secfile"])),
 
296
                              "rb")) as secfile:
297
297
                self.secret = secfile.read()
298
298
        else:
299
299
            raise TypeError(u"No secret or secfile for client %s"
396
396
        # client would inevitably timeout, since no checker would get
397
397
        # a chance to run to completion.  If we instead leave running
398
398
        # checkers alone, the checker would have to take more time
399
 
        # than 'timeout' for the client to be disabled, which is as it
400
 
        # should be.
 
399
        # than 'timeout' for the client to be declared invalid, which
 
400
        # is as it should be.
401
401
        
402
402
        # If a checker exists, make sure it is not a zombie
403
403
        try:
475
475
            if error.errno != errno.ESRCH: # No such process
476
476
                raise
477
477
        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)
478
488
 
479
489
 
480
490
def dbus_service_property(dbus_interface, signature=u"v",
489
499
    dbus.service.method, except there is only "signature", since the
490
500
    type from Get() and the type sent to Set() is the same.
491
501
    """
492
 
    # Encoding deeply encoded byte arrays is not supported yet by the
493
 
    # "Set" method, so we fail early here:
494
 
    if byte_arrays and signature != u"ay":
495
 
        raise ValueError(u"Byte arrays not supported for non-'ay'"
496
 
                         u" signature %r" % signature)
497
502
    def decorator(func):
498
503
        func._dbus_is_property = True
499
504
        func._dbus_interface = dbus_interface
585
590
        if prop._dbus_access == u"read":
586
591
            raise DBusPropertyAccessException(property_name)
587
592
        if prop._dbus_get_args_options[u"byte_arrays"]:
588
 
            # The byte_arrays option is not supported yet on
589
 
            # signatures other than "ay".
590
 
            if prop._dbus_signature != u"ay":
591
 
                raise ValueError
592
593
            value = dbus.ByteArray(''.join(unichr(byte)
593
594
                                           for byte in value))
594
595
        prop(value)
989
990
    def handle(self):
990
991
        logger.info(u"TCP connection from: %s",
991
992
                    unicode(self.client_address))
992
 
        logger.debug(u"IPC Pipe FD: %d", self.server.child_pipe[1])
 
993
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
993
994
        # Open IPC pipe to parent process
994
 
        with contextlib.nested(os.fdopen(self.server.child_pipe[1],
995
 
                                         u"w", 1),
996
 
                               os.fdopen(self.server.parent_pipe[0],
997
 
                                         u"r", 0)) as (ipc,
998
 
                                                       ipc_return):
 
995
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
999
996
            session = (gnutls.connection
1000
997
                       .ClientSession(self.request,
1001
998
                                      gnutls.connection
1036
1033
                return
1037
1034
            logger.debug(u"Handshake succeeded")
1038
1035
            try:
1039
 
                try:
1040
 
                    fpr = self.fingerprint(self.peer_certificate
1041
 
                                           (session))
1042
 
                except (TypeError, gnutls.errors.GNUTLSError), error:
1043
 
                    logger.warning(u"Bad certificate: %s", error)
1044
 
                    return
1045
 
                logger.debug(u"Fingerprint: %s", fpr)
1046
 
 
1047
 
                for c in self.server.clients:
1048
 
                    if c.fingerprint == fpr:
1049
 
                        client = c
1050
 
                        break
1051
 
                else:
1052
 
                    ipc.write(u"NOTFOUND %s %s\n"
1053
 
                              % (fpr, unicode(self.client_address)))
1054
 
                    return
1055
 
                # Have to check if client.enabled, since it is
1056
 
                # possible that the client was disabled since the
1057
 
                # GnuTLS session was established.
1058
 
                ipc.write(u"GETATTR enabled %s\n" % fpr)
1059
 
                enabled = pickle.load(ipc_return)
1060
 
                if not enabled:
1061
 
                    ipc.write(u"DISABLED %s\n" % client.name)
1062
 
                    return
1063
 
                ipc.write(u"SENDING %s\n" % client.name)
1064
 
                sent_size = 0
1065
 
                while sent_size < len(client.secret):
1066
 
                    sent = session.send(client.secret[sent_size:])
1067
 
                    logger.debug(u"Sent: %d, remaining: %d",
1068
 
                                 sent, len(client.secret)
1069
 
                                 - (sent_size + sent))
1070
 
                    sent_size += sent
1071
 
            finally:
1072
 
                session.bye()
 
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()
1073
1068
    
1074
1069
    @staticmethod
1075
1070
    def peer_certificate(session):
1135
1130
        return hex_fpr
1136
1131
 
1137
1132
 
1138
 
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1139
 
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
 
1133
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
 
1134
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
1140
1135
    def process_request(self, request, client_address):
1141
1136
        """Overrides and wraps the original process_request().
1142
1137
        
1143
1138
        This function creates a new pipe in self.pipe
1144
1139
        """
1145
 
        self.child_pipe = os.pipe() # Child writes here
1146
 
        self.parent_pipe = os.pipe() # Parent writes here
1147
 
        super(ForkingMixInWithPipes,
 
1140
        self.pipe = os.pipe()
 
1141
        super(ForkingMixInWithPipe,
1148
1142
              self).process_request(request, client_address)
1149
 
        # Close unused ends for parent
1150
 
        os.close(self.parent_pipe[0]) # close read end
1151
 
        os.close(self.child_pipe[1])  # close write end
1152
 
        self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1153
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1143
        os.close(self.pipe[1])  # close write end
 
1144
        self.add_pipe(self.pipe[0])
 
1145
    def add_pipe(self, pipe):
1154
1146
        """Dummy function; override as necessary"""
1155
 
        os.close(child_pipe_fd)
1156
 
        os.close(parent_pipe_fd)
1157
 
 
1158
 
 
1159
 
class IPv6_TCPServer(ForkingMixInWithPipes,
 
1147
        os.close(pipe)
 
1148
 
 
1149
 
 
1150
class IPv6_TCPServer(ForkingMixInWithPipe,
1160
1151
                     socketserver.TCPServer, object):
1161
1152
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1162
1153
    
1247
1238
            return socketserver.TCPServer.server_activate(self)
1248
1239
    def enable(self):
1249
1240
        self.enabled = True
1250
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1241
    def add_pipe(self, pipe):
1251
1242
        # Call "handle_ipc" for both data and EOF events
1252
 
        gobject.io_add_watch(child_pipe_fd,
1253
 
                             gobject.IO_IN | gobject.IO_HUP,
1254
 
                             functools.partial(self.handle_ipc,
1255
 
                                               reply_fd
1256
 
                                               =parent_pipe_fd))
1257
 
    def handle_ipc(self, source, condition, reply_fd=None,
1258
 
                   file_objects={}):
 
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={}):
1259
1246
        condition_names = {
1260
1247
            gobject.IO_IN: u"IN",   # There is data to read.
1261
1248
            gobject.IO_OUT: u"OUT", # Data can be written (without
1273
1260
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1274
1261
                     conditions_string)
1275
1262
        
1276
 
        # Turn the pipe file descriptors into Python file objects
 
1263
        # Turn the pipe file descriptor into a Python file object
1277
1264
        if source not in file_objects:
1278
1265
            file_objects[source] = os.fdopen(source, u"r", 1)
1279
 
        if reply_fd not in file_objects:
1280
 
            file_objects[reply_fd] = os.fdopen(reply_fd, u"w", 0)
1281
1266
        
1282
1267
        # Read a line from the file object
1283
1268
        cmdline = file_objects[source].readline()
1284
1269
        if not cmdline:             # Empty line means end of file
1285
 
            # close the IPC pipes
 
1270
            # close the IPC pipe
1286
1271
            file_objects[source].close()
1287
1272
            del file_objects[source]
1288
 
            file_objects[reply_fd].close()
1289
 
            del file_objects[reply_fd]
1290
1273
            
1291
1274
            # Stop calling this function
1292
1275
            return False
1303
1286
            if self.use_dbus:
1304
1287
                # Emit D-Bus signal
1305
1288
                mandos_dbus_service.ClientNotFound(fpr, address)
1306
 
        elif cmd == u"DISABLED":
 
1289
        elif cmd == u"INVALID":
1307
1290
            for client in self.clients:
1308
1291
                if client.name == args:
1309
 
                    logger.warning(u"Client %s is disabled", args)
 
1292
                    logger.warning(u"Client %s is invalid", args)
1310
1293
                    if self.use_dbus:
1311
1294
                        # Emit D-Bus signal
1312
1295
                        client.Rejected()
1313
1296
                    break
1314
1297
            else:
1315
 
                logger.error(u"Unknown client %s is disabled", args)
 
1298
                logger.error(u"Unknown client %s is invalid", args)
1316
1299
        elif cmd == u"SENDING":
1317
1300
            for client in self.clients:
1318
1301
                if client.name == args:
1325
1308
            else:
1326
1309
                logger.error(u"Sending secret to unknown client %s",
1327
1310
                             args)
1328
 
        elif cmd == u"GETATTR":
1329
 
            attr_name, fpr = args.split(None, 1)
1330
 
            for client in self.clients:
1331
 
                if client.fingerprint == fpr:
1332
 
                    attr_value = getattr(client, attr_name, None)
1333
 
                    logger.debug("IPC reply: %r", attr_value)
1334
 
                    pickle.dump(attr_value, file_objects[reply_fd])
1335
 
                    break
1336
 
            else:
1337
 
                logger.error(u"Client %s on address %s requesting "
1338
 
                             u"attribute %s not found", fpr, address,
1339
 
                             attr_name)
1340
 
                pickle.dump(None, file_objects[reply_fd])
1341
1311
        else:
1342
1312
            logger.error(u"Unknown IPC command: %r", cmdline)
1343
1313
        
1398
1368
        def if_nametoindex(interface):
1399
1369
            "Get an interface index the hard way, i.e. using fcntl()"
1400
1370
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1401
 
            with contextlib.closing(socket.socket()) as s:
 
1371
            with closing(socket.socket()) as s:
1402
1372
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1403
1373
                                    struct.pack(str(u"16s16x"),
1404
1374
                                                interface))
1637
1607
        daemon()
1638
1608
    
1639
1609
    try:
1640
 
        with pidfile:
 
1610
        with closing(pidfile):
1641
1611
            pid = os.getpid()
1642
1612
            pidfile.write(str(pid) + "\n")
1643
1613
        del pidfile
1661
1631
                dbus.service.Object.__init__(self, bus, u"/")
1662
1632
            _interface = u"se.bsnet.fukt.Mandos"
1663
1633
            
1664
 
            @dbus.service.signal(_interface, signature=u"o")
1665
 
            def ClientAdded(self, objpath):
 
1634
            @dbus.service.signal(_interface, signature=u"oa{sv}")
 
1635
            def ClientAdded(self, objpath, properties):
1666
1636
                "D-Bus signal"
1667
1637
                pass
1668
1638
            
1730
1700
    for client in tcp_server.clients:
1731
1701
        if use_dbus:
1732
1702
            # Emit D-Bus signal
1733
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
1703
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
 
1704
                                            client.GetAll(u""))
1734
1705
        client.enable()
1735
1706
    
1736
1707
    tcp_server.enable()