/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-01-01 15:17:12 UTC
  • Revision ID: teddy@fukt.bsnet.se-20100101151712-x20p42nhuvf9lxol
Bug fix: mandos-client needs GnuPG but lacked a dependency on it.  The
Mandos server, on the other hand, did not need GnuPG but declared an
incorrect dependency on it.

* debian/control (Package: mandos/Depends): Removed "gnupg (< 2)".
  (Package: mandos-client/Depends): Added "gnupg (<< 2)".

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
62
63
 
63
64
import dbus
64
65
import dbus.service
242
243
    enabled:    bool()
243
244
    last_checked_ok: datetime.datetime(); (UTC) or None
244
245
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
 
                                      until this client is invalid
 
246
                                      until this client is disabled
246
247
    interval:   datetime.timedelta(); How often to start a new checker
247
248
    disable_hook:  If set, called by disable() as disable_hook(self)
248
249
    checker:    subprocess.Popen(); a running checker process used
290
291
        if u"secret" in config:
291
292
            self.secret = config[u"secret"].decode(u"base64")
292
293
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])),
296
 
                              "rb")) as secfile:
 
294
            with open(os.path.expanduser(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 declared invalid, which
400
 
        # is as it should be.
 
399
        # than 'timeout' for the client to be disabled, which is as it
 
400
        # 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)
488
478
 
489
479
 
490
480
def dbus_service_property(dbus_interface, signature=u"v",
499
489
    dbus.service.method, except there is only "signature", since the
500
490
    type from Get() and the type sent to Set() is the same.
501
491
    """
 
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)
502
497
    def decorator(func):
503
498
        func._dbus_is_property = True
504
499
        func._dbus_interface = dbus_interface
590
585
        if prop._dbus_access == u"read":
591
586
            raise DBusPropertyAccessException(property_name)
592
587
        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
593
592
            value = dbus.ByteArray(''.join(unichr(byte)
594
593
                                           for byte in value))
595
594
        prop(value)
990
989
    def handle(self):
991
990
        logger.info(u"TCP connection from: %s",
992
991
                    unicode(self.client_address))
993
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
 
992
        logger.debug(u"IPC Pipe FD: %d", self.server.child_pipe[1])
994
993
        # Open IPC pipe to parent process
995
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
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):
996
999
            session = (gnutls.connection
997
1000
                       .ClientSession(self.request,
998
1001
                                      gnutls.connection
1033
1036
                return
1034
1037
            logger.debug(u"Handshake succeeded")
1035
1038
            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()
 
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()
1068
1073
    
1069
1074
    @staticmethod
1070
1075
    def peer_certificate(session):
1130
1135
        return hex_fpr
1131
1136
 
1132
1137
 
1133
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1134
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1138
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
 
1139
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1135
1140
    def process_request(self, request, client_address):
1136
1141
        """Overrides and wraps the original process_request().
1137
1142
        
1138
1143
        This function creates a new pipe in self.pipe
1139
1144
        """
1140
 
        self.pipe = os.pipe()
1141
 
        super(ForkingMixInWithPipe,
 
1145
        self.child_pipe = os.pipe() # Child writes here
 
1146
        self.parent_pipe = os.pipe() # Parent writes here
 
1147
        super(ForkingMixInWithPipes,
1142
1148
              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):
 
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):
1146
1154
        """Dummy function; override as necessary"""
1147
 
        os.close(pipe)
1148
 
 
1149
 
 
1150
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1155
        os.close(child_pipe_fd)
 
1156
        os.close(parent_pipe_fd)
 
1157
 
 
1158
 
 
1159
class IPv6_TCPServer(ForkingMixInWithPipes,
1151
1160
                     socketserver.TCPServer, object):
1152
1161
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1153
1162
    
1238
1247
            return socketserver.TCPServer.server_activate(self)
1239
1248
    def enable(self):
1240
1249
        self.enabled = True
1241
 
    def add_pipe(self, pipe):
 
1250
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1242
1251
        # 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={}):
 
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={}):
1246
1259
        condition_names = {
1247
1260
            gobject.IO_IN: u"IN",   # There is data to read.
1248
1261
            gobject.IO_OUT: u"OUT", # Data can be written (without
1260
1273
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1261
1274
                     conditions_string)
1262
1275
        
1263
 
        # Turn the pipe file descriptor into a Python file object
 
1276
        # Turn the pipe file descriptors into Python file objects
1264
1277
        if source not in file_objects:
1265
1278
            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)
1266
1281
        
1267
1282
        # Read a line from the file object
1268
1283
        cmdline = file_objects[source].readline()
1269
1284
        if not cmdline:             # Empty line means end of file
1270
 
            # close the IPC pipe
 
1285
            # close the IPC pipes
1271
1286
            file_objects[source].close()
1272
1287
            del file_objects[source]
 
1288
            file_objects[reply_fd].close()
 
1289
            del file_objects[reply_fd]
1273
1290
            
1274
1291
            # Stop calling this function
1275
1292
            return False
1280
1297
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1281
1298
        
1282
1299
        if cmd == u"NOTFOUND":
1283
 
            logger.warning(u"Client not found for fingerprint: %s",
1284
 
                           args)
 
1300
            fpr, address = args.split(None, 1)
 
1301
            logger.warning(u"Client not found for fingerprint: %s, ad"
 
1302
                           u"dress: %s", fpr, address)
1285
1303
            if self.use_dbus:
1286
1304
                # Emit D-Bus signal
1287
 
                mandos_dbus_service.ClientNotFound(args)
1288
 
        elif cmd == u"INVALID":
 
1305
                mandos_dbus_service.ClientNotFound(fpr, address)
 
1306
        elif cmd == u"DISABLED":
1289
1307
            for client in self.clients:
1290
1308
                if client.name == args:
1291
 
                    logger.warning(u"Client %s is invalid", args)
 
1309
                    logger.warning(u"Client %s is disabled", args)
1292
1310
                    if self.use_dbus:
1293
1311
                        # Emit D-Bus signal
1294
1312
                        client.Rejected()
1295
1313
                    break
1296
1314
            else:
1297
 
                logger.error(u"Unknown client %s is invalid", args)
 
1315
                logger.error(u"Unknown client %s is disabled", args)
1298
1316
        elif cmd == u"SENDING":
1299
1317
            for client in self.clients:
1300
1318
                if client.name == args:
1307
1325
            else:
1308
1326
                logger.error(u"Sending secret to unknown client %s",
1309
1327
                             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])
1310
1341
        else:
1311
1342
            logger.error(u"Unknown IPC command: %r", cmdline)
1312
1343
        
1367
1398
        def if_nametoindex(interface):
1368
1399
            "Get an interface index the hard way, i.e. using fcntl()"
1369
1400
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1370
 
            with closing(socket.socket()) as s:
 
1401
            with contextlib.closing(socket.socket()) as s:
1371
1402
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1372
1403
                                    struct.pack(str(u"16s16x"),
1373
1404
                                                interface))
1606
1637
        daemon()
1607
1638
    
1608
1639
    try:
1609
 
        with closing(pidfile):
 
1640
        with pidfile:
1610
1641
            pid = os.getpid()
1611
1642
            pidfile.write(str(pid) + "\n")
1612
1643
        del pidfile
1630
1661
                dbus.service.Object.__init__(self, bus, u"/")
1631
1662
            _interface = u"se.bsnet.fukt.Mandos"
1632
1663
            
1633
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1634
 
            def ClientAdded(self, objpath, properties):
 
1664
            @dbus.service.signal(_interface, signature=u"o")
 
1665
            def ClientAdded(self, objpath):
1635
1666
                "D-Bus signal"
1636
1667
                pass
1637
1668
            
1638
 
            @dbus.service.signal(_interface, signature=u"s")
1639
 
            def ClientNotFound(self, fingerprint):
 
1669
            @dbus.service.signal(_interface, signature=u"ss")
 
1670
            def ClientNotFound(self, fingerprint, address):
1640
1671
                "D-Bus signal"
1641
1672
                pass
1642
1673
            
1699
1730
    for client in tcp_server.clients:
1700
1731
        if use_dbus:
1701
1732
            # Emit D-Bus signal
1702
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1703
 
                                            client.GetAll(u""))
 
1733
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1704
1734
        client.enable()
1705
1735
    
1706
1736
    tcp_server.enable()