/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-10-18 08:47:40 UTC
  • Revision ID: teddy@fukt.bsnet.se-20091018084740-fa1qgm22lg125r10
* plugins.d/splashy.c: Use exit codes from <sysexits.h>.

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
80
79
        SO_BINDTODEVICE = None
81
80
 
82
81
 
83
 
version = "1.0.14"
 
82
version = "1.0.12"
84
83
 
85
84
logger = logging.Logger(u'mandos')
86
85
syslogger = (logging.handlers.SysLogHandler
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"
325
325
        self.checker_initiator_tag = (gobject.timeout_add
326
326
                                      (self.interval_milliseconds(),
327
327
                                       self.start_checker))
 
328
        # Also start a new checker *right now*.
 
329
        self.start_checker()
328
330
        # Schedule a disable() when 'timeout' has passed
329
331
        self.disable_initiator_tag = (gobject.timeout_add
330
332
                                   (self.timeout_milliseconds(),
331
333
                                    self.disable))
332
334
        self.enabled = True
333
 
        # Also start a new checker *right now*.
334
 
        self.start_checker()
335
335
    
336
 
    def disable(self, quiet=True):
 
336
    def disable(self):
337
337
        """Disable this client."""
338
338
        if not getattr(self, "enabled", False):
339
339
            return False
340
 
        if not quiet:
341
 
            logger.info(u"Disabling client %s", self.name)
 
340
        logger.info(u"Disabling client %s", self.name)
342
341
        if getattr(self, u"disable_initiator_tag", False):
343
342
            gobject.source_remove(self.disable_initiator_tag)
344
343
            self.disable_initiator_tag = None
396
395
        # client would inevitably timeout, since no checker would get
397
396
        # a chance to run to completion.  If we instead leave running
398
397
        # 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.
 
398
        # than 'timeout' for the client to be declared invalid, which
 
399
        # is as it should be.
401
400
        
402
401
        # If a checker exists, make sure it is not a zombie
403
402
        try:
468
467
        logger.debug(u"Stopping checker for %(name)s", vars(self))
469
468
        try:
470
469
            os.kill(self.checker.pid, signal.SIGTERM)
471
 
            #time.sleep(0.5)
 
470
            #os.sleep(0.5)
472
471
            #if self.checker.poll() is None:
473
472
            #    os.kill(self.checker.pid, signal.SIGKILL)
474
473
        except OSError, error:
475
474
            if error.errno != errno.ESRCH: # No such process
476
475
                raise
477
476
        self.checker = None
 
477
    
 
478
    def still_valid(self):
 
479
        """Has the timeout not yet passed for this client?"""
 
480
        if not getattr(self, u"enabled", False):
 
481
            return False
 
482
        now = datetime.datetime.utcnow()
 
483
        if self.last_checked_ok is None:
 
484
            return now < (self.created + self.timeout)
 
485
        else:
 
486
            return now < (self.last_checked_ok + self.timeout)
478
487
 
479
488
 
480
489
def dbus_service_property(dbus_interface, signature=u"v",
489
498
    dbus.service.method, except there is only "signature", since the
490
499
    type from Get() and the type sent to Set() is the same.
491
500
    """
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
501
    def decorator(func):
498
502
        func._dbus_is_property = True
499
503
        func._dbus_interface = dbus_interface
585
589
        if prop._dbus_access == u"read":
586
590
            raise DBusPropertyAccessException(property_name)
587
591
        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
592
            value = dbus.ByteArray(''.join(unichr(byte)
593
593
                                           for byte in value))
594
594
        prop(value)
705
705
                                       variant_level=1))
706
706
        return r
707
707
    
708
 
    def disable(self, quiet = False):
 
708
    def disable(self, signal = True):
709
709
        oldstate = getattr(self, u"enabled", False)
710
 
        r = Client.disable(self, quiet=quiet)
711
 
        if not quiet and oldstate != self.enabled:
 
710
        r = Client.disable(self)
 
711
        if signal and oldstate != self.enabled:
712
712
            # Emit D-Bus signal
713
713
            self.PropertyChanged(dbus.String(u"enabled"),
714
714
                                 dbus.Boolean(False, variant_level=1))
989
989
    def handle(self):
990
990
        logger.info(u"TCP connection from: %s",
991
991
                    unicode(self.client_address))
992
 
        logger.debug(u"IPC Pipe FD: %d", self.server.child_pipe[1])
 
992
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
993
993
        # 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):
 
994
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
999
995
            session = (gnutls.connection
1000
996
                       .ClientSession(self.request,
1001
997
                                      gnutls.connection
1036
1032
                return
1037
1033
            logger.debug(u"Handshake succeeded")
1038
1034
            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()
 
1035
                fpr = self.fingerprint(self.peer_certificate(session))
 
1036
            except (TypeError, gnutls.errors.GNUTLSError), error:
 
1037
                logger.warning(u"Bad certificate: %s", error)
 
1038
                session.bye()
 
1039
                return
 
1040
            logger.debug(u"Fingerprint: %s", fpr)
 
1041
            
 
1042
            for c in self.server.clients:
 
1043
                if c.fingerprint == fpr:
 
1044
                    client = c
 
1045
                    break
 
1046
            else:
 
1047
                ipc.write(u"NOTFOUND %s %s\n"
 
1048
                          % (fpr, unicode(self.client_address)))
 
1049
                session.bye()
 
1050
                return
 
1051
            # Have to check if client.still_valid(), since it is
 
1052
            # possible that the client timed out while establishing
 
1053
            # the GnuTLS session.
 
1054
            if not client.still_valid():
 
1055
                ipc.write(u"INVALID %s\n" % client.name)
 
1056
                session.bye()
 
1057
                return
 
1058
            ipc.write(u"SENDING %s\n" % client.name)
 
1059
            sent_size = 0
 
1060
            while sent_size < len(client.secret):
 
1061
                sent = session.send(client.secret[sent_size:])
 
1062
                logger.debug(u"Sent: %d, remaining: %d",
 
1063
                             sent, len(client.secret)
 
1064
                             - (sent_size + sent))
 
1065
                sent_size += sent
 
1066
            session.bye()
1073
1067
    
1074
1068
    @staticmethod
1075
1069
    def peer_certificate(session):
1135
1129
        return hex_fpr
1136
1130
 
1137
1131
 
1138
 
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1139
 
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
 
1132
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
 
1133
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
1140
1134
    def process_request(self, request, client_address):
1141
1135
        """Overrides and wraps the original process_request().
1142
1136
        
1143
1137
        This function creates a new pipe in self.pipe
1144
1138
        """
1145
 
        self.child_pipe = os.pipe() # Child writes here
1146
 
        self.parent_pipe = os.pipe() # Parent writes here
1147
 
        super(ForkingMixInWithPipes,
 
1139
        self.pipe = os.pipe()
 
1140
        super(ForkingMixInWithPipe,
1148
1141
              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):
 
1142
        os.close(self.pipe[1])  # close write end
 
1143
        self.add_pipe(self.pipe[0])
 
1144
    def add_pipe(self, pipe):
1154
1145
        """Dummy function; override as necessary"""
1155
 
        os.close(child_pipe_fd)
1156
 
        os.close(parent_pipe_fd)
1157
 
 
1158
 
 
1159
 
class IPv6_TCPServer(ForkingMixInWithPipes,
 
1146
        os.close(pipe)
 
1147
 
 
1148
 
 
1149
class IPv6_TCPServer(ForkingMixInWithPipe,
1160
1150
                     socketserver.TCPServer, object):
1161
1151
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1162
1152
    
1247
1237
            return socketserver.TCPServer.server_activate(self)
1248
1238
    def enable(self):
1249
1239
        self.enabled = True
1250
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1240
    def add_pipe(self, pipe):
1251
1241
        # 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={}):
 
1242
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
 
1243
                             self.handle_ipc)
 
1244
    def handle_ipc(self, source, condition, file_objects={}):
1259
1245
        condition_names = {
1260
1246
            gobject.IO_IN: u"IN",   # There is data to read.
1261
1247
            gobject.IO_OUT: u"OUT", # Data can be written (without
1273
1259
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1274
1260
                     conditions_string)
1275
1261
        
1276
 
        # Turn the pipe file descriptors into Python file objects
 
1262
        # Turn the pipe file descriptor into a Python file object
1277
1263
        if source not in file_objects:
1278
1264
            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
1265
        
1282
1266
        # Read a line from the file object
1283
1267
        cmdline = file_objects[source].readline()
1284
1268
        if not cmdline:             # Empty line means end of file
1285
 
            # close the IPC pipes
 
1269
            # close the IPC pipe
1286
1270
            file_objects[source].close()
1287
1271
            del file_objects[source]
1288
 
            file_objects[reply_fd].close()
1289
 
            del file_objects[reply_fd]
1290
1272
            
1291
1273
            # Stop calling this function
1292
1274
            return False
1297
1279
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1298
1280
        
1299
1281
        if cmd == u"NOTFOUND":
1300
 
            fpr, address = args.split(None, 1)
1301
 
            logger.warning(u"Client not found for fingerprint: %s, ad"
1302
 
                           u"dress: %s", fpr, address)
 
1282
            logger.warning(u"Client not found for fingerprint: %s",
 
1283
                           args)
1303
1284
            if self.use_dbus:
1304
1285
                # Emit D-Bus signal
1305
 
                mandos_dbus_service.ClientNotFound(fpr, address)
1306
 
        elif cmd == u"DISABLED":
 
1286
                mandos_dbus_service.ClientNotFound(args)
 
1287
        elif cmd == u"INVALID":
1307
1288
            for client in self.clients:
1308
1289
                if client.name == args:
1309
 
                    logger.warning(u"Client %s is disabled", args)
 
1290
                    logger.warning(u"Client %s is invalid", args)
1310
1291
                    if self.use_dbus:
1311
1292
                        # Emit D-Bus signal
1312
1293
                        client.Rejected()
1313
1294
                    break
1314
1295
            else:
1315
 
                logger.error(u"Unknown client %s is disabled", args)
 
1296
                logger.error(u"Unknown client %s is invalid", args)
1316
1297
        elif cmd == u"SENDING":
1317
1298
            for client in self.clients:
1318
1299
                if client.name == args:
1325
1306
            else:
1326
1307
                logger.error(u"Sending secret to unknown client %s",
1327
1308
                             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
1309
        else:
1342
1310
            logger.error(u"Unknown IPC command: %r", cmdline)
1343
1311
        
1377
1345
            elif suffix == u"w":
1378
1346
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1379
1347
            else:
1380
 
                raise ValueError(u"Unknown suffix %r" % suffix)
1381
 
        except (ValueError, IndexError), e:
1382
 
            raise ValueError(e.message)
 
1348
                raise ValueError
 
1349
        except (ValueError, IndexError):
 
1350
            raise ValueError
1383
1351
        timevalue += delta
1384
1352
    return timevalue
1385
1353
 
1398
1366
        def if_nametoindex(interface):
1399
1367
            "Get an interface index the hard way, i.e. using fcntl()"
1400
1368
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1401
 
            with contextlib.closing(socket.socket()) as s:
 
1369
            with closing(socket.socket()) as s:
1402
1370
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1403
1371
                                    struct.pack(str(u"16s16x"),
1404
1372
                                                interface))
1598
1566
    bus = dbus.SystemBus()
1599
1567
    # End of Avahi example code
1600
1568
    if use_dbus:
1601
 
        try:
1602
 
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1603
 
                                            bus, do_not_queue=True)
1604
 
        except dbus.exceptions.NameExistsException, e:
1605
 
            logger.error(unicode(e) + u", disabling D-Bus")
1606
 
            use_dbus = False
1607
 
            server_settings[u"use_dbus"] = False
1608
 
            tcp_server.use_dbus = False
 
1569
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1609
1570
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1610
1571
    service = AvahiService(name = server_settings[u"servicename"],
1611
1572
                           servicetype = u"_mandos._tcp",
1637
1598
        daemon()
1638
1599
    
1639
1600
    try:
1640
 
        with pidfile:
 
1601
        with closing(pidfile):
1641
1602
            pid = os.getpid()
1642
1603
            pidfile.write(str(pid) + "\n")
1643
1604
        del pidfile
1649
1610
        pass
1650
1611
    del pidfilename
1651
1612
    
 
1613
    def cleanup():
 
1614
        "Cleanup function; run on exit"
 
1615
        service.cleanup()
 
1616
        
 
1617
        while tcp_server.clients:
 
1618
            client = tcp_server.clients.pop()
 
1619
            client.disable_hook = None
 
1620
            client.disable()
 
1621
    
 
1622
    atexit.register(cleanup)
 
1623
    
1652
1624
    if not debug:
1653
1625
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1654
1626
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1661
1633
                dbus.service.Object.__init__(self, bus, u"/")
1662
1634
            _interface = u"se.bsnet.fukt.Mandos"
1663
1635
            
1664
 
            @dbus.service.signal(_interface, signature=u"o")
1665
 
            def ClientAdded(self, objpath):
 
1636
            @dbus.service.signal(_interface, signature=u"oa{sv}")
 
1637
            def ClientAdded(self, objpath, properties):
1666
1638
                "D-Bus signal"
1667
1639
                pass
1668
1640
            
1669
 
            @dbus.service.signal(_interface, signature=u"ss")
1670
 
            def ClientNotFound(self, fingerprint, address):
 
1641
            @dbus.service.signal(_interface, signature=u"s")
 
1642
            def ClientNotFound(self, fingerprint):
1671
1643
                "D-Bus signal"
1672
1644
                pass
1673
1645
            
1699
1671
                        tcp_server.clients.remove(c)
1700
1672
                        c.remove_from_connection()
1701
1673
                        # Don't signal anything except ClientRemoved
1702
 
                        c.disable(quiet=True)
 
1674
                        c.disable(signal=False)
1703
1675
                        # Emit D-Bus signal
1704
1676
                        self.ClientRemoved(object_path, c.name)
1705
1677
                        return
1706
 
                raise KeyError(object_path)
 
1678
                raise KeyError
1707
1679
            
1708
1680
            del _interface
1709
1681
        
1710
1682
        mandos_dbus_service = MandosDBusService()
1711
1683
    
1712
 
    def cleanup():
1713
 
        "Cleanup function; run on exit"
1714
 
        service.cleanup()
1715
 
        
1716
 
        while tcp_server.clients:
1717
 
            client = tcp_server.clients.pop()
1718
 
            if use_dbus:
1719
 
                client.remove_from_connection()
1720
 
            client.disable_hook = None
1721
 
            # Don't signal anything except ClientRemoved
1722
 
            client.disable(quiet=True)
1723
 
            if use_dbus:
1724
 
                # Emit D-Bus signal
1725
 
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1726
 
                                                  client.name)
1727
 
    
1728
 
    atexit.register(cleanup)
1729
 
    
1730
1684
    for client in tcp_server.clients:
1731
1685
        if use_dbus:
1732
1686
            # Emit D-Bus signal
1733
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
1687
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
 
1688
                                            client.GetAll(u""))
1734
1689
        client.enable()
1735
1690
    
1736
1691
    tcp_server.enable()
1754
1709
            service.activate()
1755
1710
        except dbus.exceptions.DBusException, error:
1756
1711
            logger.critical(u"DBusException: %s", error)
1757
 
            cleanup()
1758
1712
            sys.exit(1)
1759
1713
        # End of Avahi example code
1760
1714
        
1767
1721
        main_loop.run()
1768
1722
    except AvahiError, error:
1769
1723
        logger.critical(u"AvahiError: %s", error)
1770
 
        cleanup()
1771
1724
        sys.exit(1)
1772
1725
    except KeyboardInterrupt:
1773
1726
        if debug:
1774
1727
            print >> sys.stderr
1775
1728
        logger.debug(u"Server received KeyboardInterrupt")
1776
1729
    logger.debug(u"Server exiting")
1777
 
    # Must run before the D-Bus bus name gets deregistered
1778
 
    cleanup()
1779
1730
 
1780
1731
if __name__ == '__main__':
1781
1732
    main()