RSS

(root)/trunk : 411

Teddy Hogeborn
2009-12-26 00:13:47
Revision ID: teddy@fukt.bsnet.se-20091225231347-gg9u9ru0wj0f24hh
More consistent terminology: Clients are no longer "invalid" - they are "disabled".  All code and documentation changed to reflect this. D=Bus API change: The "properties" argument was removed from the "ClientAdded" signal on interface "se.bsnet.fukt.Mandos".  All code in both "mandos" and "mandos-monitor" changed to reflect this. * mandos: Replaced "with closing(F)" with simply "with F" in all           places where F is a file object.   (Client.still_valid): Removed.  All callers changed to look at                         "Client.enabled" instead.   (dbus_service_property): Check for unsupported signatures with the                            "byte_arrays" option.   (DBusObjectWithProperties.Set): - '' -   (ClientHandler.handle): Use the reverse pipe to receive the                           "Client.enabled" attribute instead of the                           now-removed "Client.still_valid()" method.   (ForkingMixInWithPipe): Renamed to "ForkingMixInWithPipes" (all                           users changed).  Now also create a reverse                           pipe for sending data to the child process.   (ForkingMixInWithPipes.add_pipe): Now takes two pipe fd's as                                     arguments.  All callers changed.   (IPv6_TCPServer.handle_ipc): Take an additional "reply_fd" argument                                (all callers changed).  Close the reply                                pipe when the child data pipe is                                closed.  New "GETATTR" IPC method; will                                pickle client attribute and send it                                over the reply pipe FD.   (MandosDBusService.ClientAdded): Removed "properties" argument.  All                                    emitters changed. * mandos-clients.conf.xml (DESCRIPTION, OPTIONS): Use                                                   "enabled/disabled"                                                   terminology. * mandos-ctl: Option "--is-valid" renamed to "--is-enabled". * mandos-monitor: Enable user locale.  Try to log exceptions.   (MandosClientPropertyCache.__init__): Removed "properties" argument.                                         All callers changed.   (UserInterface.add_new_client): Remove "properties" argument.  All                                   callers changed.  Supply "logger"                                   argument to MandosClientWidget().   (UserInterface.add_client): New "logger" argument.  All callers                               changed. * mandos.xml (BUGS, SECURITY/CLIENTS): Use "enabled/disabled"                                        terminology.

collapse all collapse all

added added

removed removed

68
   secret
68
   secret
69
** TODO Persistent state
69
** TODO Persistent state
70
   /var/lib/mandos/*
70
   /var/lib/mandos/*
 
 
71
** TODO Support RFC 3339 time duration syntax
71
 
72
 
72
* mandos.xml
73
* mandos.xml
73
** [[file:mandos.xml::XXX][Document D-Bus interface]]
74
** [[file:mandos.xml::XXX][Document D-Bus interface]]
77
* mandos-ctl
78
* mandos-ctl
78
*** Handle "no D-Bus server" and/or "no Mandos server found" better
79
*** Handle "no D-Bus server" and/or "no Mandos server found" better
79
*** [#B] --dump option
80
*** [#B] --dump option
 
 
81
** TODO Support RFC 3339 time duration syntax
80
 
82
 
81
* TODO mandos-dispatch
83
* TODO mandos-dispatch
82
  Listens for specified D-Bus signals and spawns shell commands with
84
  Listens for specified D-Bus signals and spawns shell commands with
83
  arguments.
85
  arguments.
84
 
86
 
85
* mandos-monitor
87
* mandos-monitor
86
** D-Bus main loop w/ signal receiver
 
 
87
** Urwid client data displayer
88
** Urwid client data displayer
88
*** Urwid scaffolding
 
 
89
*** Client Widgets
 
 
90
*** Properties popup
89
*** Properties popup
91
 
90
 
92
* mandos-keygen
91
* mandos-keygen
2
# values, so uncomment and change them if you want different ones.
2
# values, so uncomment and change them if you want different ones.
3
[DEFAULT]
3
[DEFAULT]
4
 
4
 
5
# How long until a client is considered invalid - that is, ineligible
5
# How long until a client is disabled and not be allowed to get the
6
# to get the data this server holds.
6
# data this server holds.
7
;timeout = 1h
7
;timeout = 1h
8
 
8
 
9
# How often to run the checker to confirm that a client is still up.
9
# How often to run the checker to confirm that a client is still up.
10
# Note: a new checker will not be started if an old one is still
10
# Note: a new checker will not be started if an old one is still
11
# running.  The server will wait for a checker to complete until the
11
# running.  The server will wait for a checker to complete until the
12
# above "timeout" occurs, at which time the client will be marked
12
# above "timeout" occurs, at which time the client will be disabled,
13
# invalid, and any running checker killed.
13
# and any running checker killed.
14
;interval = 5m
14
;interval = 5m
15
 
15
 
16
# What command to run as "the checker".
16
# What command to run as "the checker".
55
import logging
55
import logging
56
import logging.handlers
56
import logging.handlers
57
import pwd
57
import pwd
58
from contextlib import closing
58
import contextlib
59
import struct
59
import struct
60
import fcntl
60
import fcntl
61
import functools
61
import functools
 
 
62
import cPickle as pickle
62
 
63
 
63
import dbus
64
import dbus
64
import dbus.service
65
import dbus.service
242
    enabled:    bool()
243
    enabled:    bool()
243
    last_checked_ok: datetime.datetime(); (UTC) or None
244
    last_checked_ok: datetime.datetime(); (UTC) or None
244
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
                                      until this client is invalid
246
                                      until this client is disabled
246
    interval:   datetime.timedelta(); How often to start a new checker
247
    interval:   datetime.timedelta(); How often to start a new checker
247
    disable_hook:  If set, called by disable() as disable_hook(self)
248
    disable_hook:  If set, called by disable() as disable_hook(self)
248
    checker:    subprocess.Popen(); a running checker process used
249
    checker:    subprocess.Popen(); a running checker process used
290
        if u"secret" in config:
291
        if u"secret" in config:
291
            self.secret = config[u"secret"].decode(u"base64")
292
            self.secret = config[u"secret"].decode(u"base64")
292
        elif u"secfile" in config:
293
        elif u"secfile" in config:
293
            with closing(open(os.path.expanduser
294
            with open(os.path.expanduser(os.path.expandvars
294
                              (os.path.expandvars
295
                                         (config[u"secfile"])),
295
                               (config[u"secfile"])),
296
                      "rb") as secfile:
296
                              "rb")) as secfile:
 
 
297
                self.secret = secfile.read()
297
                self.secret = secfile.read()
298
        else:
298
        else:
299
            raise TypeError(u"No secret or secfile for client %s"
299
            raise TypeError(u"No secret or secfile for client %s"
396
        # client would inevitably timeout, since no checker would get
396
        # client would inevitably timeout, since no checker would get
397
        # a chance to run to completion.  If we instead leave running
397
        # a chance to run to completion.  If we instead leave running
398
        # checkers alone, the checker would have to take more time
398
        # checkers alone, the checker would have to take more time
399
        # than 'timeout' for the client to be declared invalid, which
399
        # than 'timeout' for the client to be disabled, which is as it
400
        # is as it should be.
400
        # should be.
401
        
401
        
402
        # If a checker exists, make sure it is not a zombie
402
        # If a checker exists, make sure it is not a zombie
403
        try:
403
        try:
475
            if error.errno != errno.ESRCH: # No such process
475
            if error.errno != errno.ESRCH: # No such process
476
                raise
476
                raise
477
        self.checker = None
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
def dbus_service_property(dbus_interface, signature=u"v",
480
def dbus_service_property(dbus_interface, signature=u"v",
499
    dbus.service.method, except there is only "signature", since the
489
    dbus.service.method, except there is only "signature", since the
500
    type from Get() and the type sent to Set() is the same.
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
    def decorator(func):
497
    def decorator(func):
503
        func._dbus_is_property = True
498
        func._dbus_is_property = True
504
        func._dbus_interface = dbus_interface
499
        func._dbus_interface = dbus_interface
590
        if prop._dbus_access == u"read":
585
        if prop._dbus_access == u"read":
591
            raise DBusPropertyAccessException(property_name)
586
            raise DBusPropertyAccessException(property_name)
592
        if prop._dbus_get_args_options[u"byte_arrays"]:
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
            value = dbus.ByteArray(''.join(unichr(byte)
592
            value = dbus.ByteArray(''.join(unichr(byte)
594
                                           for byte in value))
593
                                           for byte in value))
595
        prop(value)
594
        prop(value)
990
    def handle(self):
989
    def handle(self):
991
        logger.info(u"TCP connection from: %s",
990
        logger.info(u"TCP connection from: %s",
992
                    unicode(self.client_address))
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
        # Open IPC pipe to parent process
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
            session = (gnutls.connection
999
            session = (gnutls.connection
997
                       .ClientSession(self.request,
1000
                       .ClientSession(self.request,
998
                                      gnutls.connection
1001
                                      gnutls.connection
1033
                return
1036
                return
1034
            logger.debug(u"Handshake succeeded")
1037
            logger.debug(u"Handshake succeeded")
1035
            try:
1038
            try:
1036
                fpr = self.fingerprint(self.peer_certificate(session))
1039
                try:
1037
            except (TypeError, gnutls.errors.GNUTLSError), error:
1040
                    fpr = self.fingerprint(self.peer_certificate
1038
                logger.warning(u"Bad certificate: %s", error)
1041
                                           (session))
1039
                session.bye()
1042
                except (TypeError, gnutls.errors.GNUTLSError), error:
1040
                return
1043
                    logger.warning(u"Bad certificate: %s", error)
1041
            logger.debug(u"Fingerprint: %s", fpr)
1044
                    return
1042
            
1045
                logger.debug(u"Fingerprint: %s", fpr)
1043
            for c in self.server.clients:
1046
 
1044
                if c.fingerprint == fpr:
1047
                for c in self.server.clients:
1045
                    client = c
1048
                    if c.fingerprint == fpr:
1046
                    break
1049
                        client = c
1047
            else:
1050
                        break
1048
                ipc.write(u"NOTFOUND %s %s\n"
1051
                else:
1049
                          % (fpr, unicode(self.client_address)))
1052
                    ipc.write(u"NOTFOUND %s %s\n"
1050
                session.bye()
1053
                              % (fpr, unicode(self.client_address)))
1051
                return
1054
                    return
1052
            # Have to check if client.still_valid(), since it is
1055
                # Have to check if client.enabled, since it is
1053
            # possible that the client timed out while establishing
1056
                # possible that the client was disabled since the
1054
            # the GnuTLS session.
1057
                # GnuTLS session was established.
1055
            if not client.still_valid():
1058
                ipc.write(u"GETATTR enabled %s\n" % fpr)
1056
                ipc.write(u"INVALID %s\n" % client.name)
1059
                enabled = pickle.load(ipc_return)
1057
                session.bye()
1060
                if not enabled:
1058
                return
1061
                    ipc.write(u"DISABLED %s\n" % client.name)
1059
            ipc.write(u"SENDING %s\n" % client.name)
1062
                    return
1060
            sent_size = 0
1063
                ipc.write(u"SENDING %s\n" % client.name)
1061
            while sent_size < len(client.secret):
1064
                sent_size = 0
1062
                sent = session.send(client.secret[sent_size:])
1065
                while sent_size < len(client.secret):
1063
                logger.debug(u"Sent: %d, remaining: %d",
1066
                    sent = session.send(client.secret[sent_size:])
1064
                             sent, len(client.secret)
1067
                    logger.debug(u"Sent: %d, remaining: %d",
1065
                             - (sent_size + sent))
1068
                                 sent, len(client.secret)
1066
                sent_size += sent
1069
                                 - (sent_size + sent))
1067
            session.bye()
1070
                    sent_size += sent
 
 
1071
            finally:
 
 
1072
                session.bye()
1068
    
1073
    
1069
    @staticmethod
1074
    @staticmethod
1070
    def peer_certificate(session):
1075
    def peer_certificate(session):
1130
        return hex_fpr
1135
        return hex_fpr
1131
 
1136
 
1132
 
1137
 
1133
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1138
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1134
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
1139
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1135
    def process_request(self, request, client_address):
1140
    def process_request(self, request, client_address):
1136
        """Overrides and wraps the original process_request().
1141
        """Overrides and wraps the original process_request().
1137
        
1142
        
1138
        This function creates a new pipe in self.pipe
1143
        This function creates a new pipe in self.pipe
1139
        """
1144
        """
1140
        self.pipe = os.pipe()
1145
        self.child_pipe = os.pipe() # Child writes here
1141
        super(ForkingMixInWithPipe,
1146
        self.parent_pipe = os.pipe() # Parent writes here
 
 
1147
        super(ForkingMixInWithPipes,
1142
              self).process_request(request, client_address)
1148
              self).process_request(request, client_address)
1143
        os.close(self.pipe[1])  # close write end
1149
        # Close unused ends for parent
1144
        self.add_pipe(self.pipe[0])
1150
        os.close(self.parent_pipe[0]) # close read end
1145
    def add_pipe(self, pipe):
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
        """Dummy function; override as necessary"""
1154
        """Dummy function; override as necessary"""
1147
        os.close(pipe)
1155
        os.close(child_pipe_fd)
1148
 
1156
        os.close(parent_pipe_fd)
1149
 
1157
 
1150
class IPv6_TCPServer(ForkingMixInWithPipe,
1158
 
 
 
1159
class IPv6_TCPServer(ForkingMixInWithPipes,
1151
                     socketserver.TCPServer, object):
1160
                     socketserver.TCPServer, object):
1152
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1161
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1153
    
1162
    
1238
            return socketserver.TCPServer.server_activate(self)
1247
            return socketserver.TCPServer.server_activate(self)
1239
    def enable(self):
1248
    def enable(self):
1240
        self.enabled = True
1249
        self.enabled = True
1241
    def add_pipe(self, pipe):
1250
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1242
        # Call "handle_ipc" for both data and EOF events
1251
        # Call "handle_ipc" for both data and EOF events
1243
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1252
        gobject.io_add_watch(child_pipe_fd,
1244
                             self.handle_ipc)
1253
                             gobject.IO_IN | gobject.IO_HUP,
1245
    def handle_ipc(self, source, condition, file_objects={}):
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
        condition_names = {
1259
        condition_names = {
1247
            gobject.IO_IN: u"IN",   # There is data to read.
1260
            gobject.IO_IN: u"IN",   # There is data to read.
1248
            gobject.IO_OUT: u"OUT", # Data can be written (without
1261
            gobject.IO_OUT: u"OUT", # Data can be written (without
1260
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1273
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1261
                     conditions_string)
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
        if source not in file_objects:
1277
        if source not in file_objects:
1265
            file_objects[source] = os.fdopen(source, u"r", 1)
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
        # Read a line from the file object
1282
        # Read a line from the file object
1268
        cmdline = file_objects[source].readline()
1283
        cmdline = file_objects[source].readline()
1269
        if not cmdline:             # Empty line means end of file
1284
        if not cmdline:             # Empty line means end of file
1270
            # close the IPC pipe
1285
            # close the IPC pipes
1271
            file_objects[source].close()
1286
            file_objects[source].close()
1272
            del file_objects[source]
1287
            del file_objects[source]
 
 
1288
            file_objects[reply_fd].close()
 
 
1289
            del file_objects[reply_fd]
1273
            
1290
            
1274
            # Stop calling this function
1291
            # Stop calling this function
1275
            return False
1292
            return False
1286
            if self.use_dbus:
1303
            if self.use_dbus:
1287
                # Emit D-Bus signal
1304
                # Emit D-Bus signal
1288
                mandos_dbus_service.ClientNotFound(fpr, address)
1305
                mandos_dbus_service.ClientNotFound(fpr, address)
1289
        elif cmd == u"INVALID":
1306
        elif cmd == u"DISABLED":
1290
            for client in self.clients:
1307
            for client in self.clients:
1291
                if client.name == args:
1308
                if client.name == args:
1292
                    logger.warning(u"Client %s is invalid", args)
1309
                    logger.warning(u"Client %s is disabled", args)
1293
                    if self.use_dbus:
1310
                    if self.use_dbus:
1294
                        # Emit D-Bus signal
1311
                        # Emit D-Bus signal
1295
                        client.Rejected()
1312
                        client.Rejected()
1296
                    break
1313
                    break
1297
            else:
1314
            else:
1298
                logger.error(u"Unknown client %s is invalid", args)
1315
                logger.error(u"Unknown client %s is disabled", args)
1299
        elif cmd == u"SENDING":
1316
        elif cmd == u"SENDING":
1300
            for client in self.clients:
1317
            for client in self.clients:
1301
                if client.name == args:
1318
                if client.name == args:
1308
            else:
1325
            else:
1309
                logger.error(u"Sending secret to unknown client %s",
1326
                logger.error(u"Sending secret to unknown client %s",
1310
                             args)
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])
1311
        else:
1341
        else:
1312
            logger.error(u"Unknown IPC command: %r", cmdline)
1342
            logger.error(u"Unknown IPC command: %r", cmdline)
1313
        
1343
        
1368
        def if_nametoindex(interface):
1398
        def if_nametoindex(interface):
1369
            "Get an interface index the hard way, i.e. using fcntl()"
1399
            "Get an interface index the hard way, i.e. using fcntl()"
1370
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1400
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1371
            with closing(socket.socket()) as s:
1401
            with contextlib.closing(socket.socket()) as s:
1372
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1402
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1373
                                    struct.pack(str(u"16s16x"),
1403
                                    struct.pack(str(u"16s16x"),
1374
                                                interface))
1404
                                                interface))
1607
        daemon()
1637
        daemon()
1608
    
1638
    
1609
    try:
1639
    try:
1610
        with closing(pidfile):
1640
        with pidfile:
1611
            pid = os.getpid()
1641
            pid = os.getpid()
1612
            pidfile.write(str(pid) + "\n")
1642
            pidfile.write(str(pid) + "\n")
1613
        del pidfile
1643
        del pidfile
1631
                dbus.service.Object.__init__(self, bus, u"/")
1661
                dbus.service.Object.__init__(self, bus, u"/")
1632
            _interface = u"se.bsnet.fukt.Mandos"
1662
            _interface = u"se.bsnet.fukt.Mandos"
1633
            
1663
            
1634
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1664
            @dbus.service.signal(_interface, signature=u"o")
1635
            def ClientAdded(self, objpath, properties):
1665
            def ClientAdded(self, objpath):
1636
                "D-Bus signal"
1666
                "D-Bus signal"
1637
                pass
1667
                pass
1638
            
1668
            
1700
    for client in tcp_server.clients:
1730
    for client in tcp_server.clients:
1701
        if use_dbus:
1731
        if use_dbus:
1702
            # Emit D-Bus signal
1732
            # Emit D-Bus signal
1703
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1733
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1704
                                            client.GetAll(u""))
 
 
1705
        client.enable()
1734
        client.enable()
1706
    
1735
    
1707
    tcp_server.enable()
1736
    tcp_server.enable()
3
        "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
3
        "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
4
<!ENTITY CONFNAME "mandos-clients.conf">
4
<!ENTITY CONFNAME "mandos-clients.conf">
5
<!ENTITY CONFPATH "<filename>/etc/mandos/clients.conf</filename>">
5
<!ENTITY CONFPATH "<filename>/etc/mandos/clients.conf</filename>">
6
<!ENTITY TIMESTAMP "2009-09-17">
6
<!ENTITY TIMESTAMP "2009-12-09">
7
<!ENTITY % common SYSTEM "common.ent">
7
<!ENTITY % common SYSTEM "common.ent">
8
%common;
8
%common;
9
]>
9
]>
63
      ><refentrytitle>mandos</refentrytitle>
63
      ><refentrytitle>mandos</refentrytitle>
64
      <manvolnum>8</manvolnum></citerefentry>, read by it at startup.
64
      <manvolnum>8</manvolnum></citerefentry>, read by it at startup.
65
      The file needs to list all clients that should be able to use
65
      The file needs to list all clients that should be able to use
66
      the service.  All clients listed will be regarded as valid, even
66
      the service.  All clients listed will be regarded as enabled,
67
      if a client was declared invalid in a previous run of the
67
      even if a client was disabled in a previous run of the server.
68
      server.
 
 
69
    </para>
68
    </para>
70
    <para>
69
    <para>
71
      The format starts with a <literal>[<replaceable>section
70
      The format starts with a <literal>[<replaceable>section
110
          <para>
109
          <para>
111
            The timeout is how long the server will wait (for either a
110
            The timeout is how long the server will wait (for either a
112
            successful checker run or a client receiving its secret)
111
            successful checker run or a client receiving its secret)
113
            until a client is considered invalid - that is, ineligible
112
            until a client is disabled and not allowed to get the data
114
            to get the data this server holds.  By default Mandos will
113
            this server holds.  By default Mandos will use 1 hour.
115
            use 1 hour.
 
 
116
          </para>
114
          </para>
117
          <para>
115
          <para>
118
            The <replaceable>TIME</replaceable> is specified as a
116
            The <replaceable>TIME</replaceable> is specified as a
143
            not be started if an old one is still running.  The server
141
            not be started if an old one is still running.  The server
144
            will wait for a checker to complete until the above
142
            will wait for a checker to complete until the above
145
            <quote><varname>timeout</varname></quote> occurs, at which
143
            <quote><varname>timeout</varname></quote> occurs, at which
146
            time the client will be marked invalid, and any running
144
            time the client will be disabled, and any running checker
147
            checker killed.  The default interval is 5 minutes.
145
            killed.  The default interval is 5 minutes.
148
          </para>
146
          </para>
149
          <para>
147
          <para>
150
            The format of <replaceable>TIME</replaceable> is the same
148
            The format of <replaceable>TIME</replaceable> is the same
128
                  help="Start checker for client")
128
                  help="Start checker for client")
129
parser.add_option("--stop-checker", action="store_true",
129
parser.add_option("--stop-checker", action="store_true",
130
                  help="Stop checker for client")
130
                  help="Stop checker for client")
131
parser.add_option("-V", "--is-valid", action="store_true",
131
parser.add_option("-V", "--is-enabled", action="store_true",
132
                  help="Check if client is still valid")
132
                  help="Check if client is enabled")
133
parser.add_option("-r", "--remove", action="store_true",
133
parser.add_option("-r", "--remove", action="store_true",
134
                  help="Remove client")
134
                  help="Remove client")
135
parser.add_option("-c", "--checker", type="string",
135
parser.add_option("-c", "--checker", type="string",
178
        client.StartChecker(dbus_interface=client_interface)
178
        client.StartChecker(dbus_interface=client_interface)
179
    if options.stop_checker:
179
    if options.stop_checker:
180
        client.StopChecker(dbus_interface=client_interface)
180
        client.StopChecker(dbus_interface=client_interface)
181
    if options.is_valid:
181
    if options.is_enabled:
182
        sys.exit(0 if client.Get(client_interface,
182
        sys.exit(0 if client.Get(client_interface,
183
                                 u"enabled",
183
                                 u"enabled",
184
                                 dbus_interface=dbus.PROPERTIES_IFACE)
184
                                 dbus_interface=dbus.PROPERTIES_IFACE)
19
 
19
 
20
import UserList
20
import UserList
21
 
21
 
 
 
22
import locale
 
 
23
 
 
 
24
locale.setlocale(locale.LC_ALL, u'')
 
 
25
 
22
# Some useful constants
26
# Some useful constants
23
domain = 'se.bsnet.fukt'
27
domain = 'se.bsnet.fukt'
24
server_interface = domain + '.Mandos'
28
server_interface = domain + '.Mandos'
38
    properties and calls a hook function when any of them are
42
    properties and calls a hook function when any of them are
39
    changed.
43
    changed.
40
    """
44
    """
41
    def __init__(self, proxy_object=None, properties=None, *args,
45
    def __init__(self, proxy_object=None, *args, **kwargs):
42
                 **kwargs):
 
 
43
        self.proxy = proxy_object # Mandos Client proxy object
46
        self.proxy = proxy_object # Mandos Client proxy object
44
        
47
        
45
        if properties is None:
48
        self.properties = dict()
46
            self.properties = dict()
 
 
47
        else:
 
 
48
            self.properties = properties
 
 
49
        self.proxy.connect_to_signal(u"PropertyChanged",
49
        self.proxy.connect_to_signal(u"PropertyChanged",
50
                                     self.property_changed,
50
                                     self.property_changed,
51
                                     client_interface,
51
                                     client_interface,
52
                                     byte_arrays=True)
52
                                     byte_arrays=True)
53
        
53
 
54
        if properties is None:
54
        self.properties.update(
55
            self.properties.update(self.proxy.GetAll(client_interface,
55
            self.proxy.GetAll(client_interface,
56
                                                     dbus_interface =
56
                              dbus_interface = dbus.PROPERTIES_IFACE))
57
                                                     dbus.PROPERTIES_IFACE))
 
 
58
        super(MandosClientPropertyCache, self).__init__(
57
        super(MandosClientPropertyCache, self).__init__(
59
            proxy_object=proxy_object,
58
            proxy_object=proxy_object, *args, **kwargs)
60
            properties=properties, *args, **kwargs)
 
 
61
    
59
    
62
    def property_changed(self, property=None, value=None):
60
    def property_changed(self, property=None, value=None):
63
        """This is called whenever we get a PropertyChanged signal
61
        """This is called whenever we get a PropertyChanged signal
424
            return
422
            return
425
        self.remove_client(client, path)
423
        self.remove_client(client, path)
426
    
424
    
427
    def add_new_client(self, path, properties):
425
    def add_new_client(self, path):
428
        client_proxy_object = self.bus.get_object(self.busname, path)
426
        client_proxy_object = self.bus.get_object(self.busname, path)
429
        self.add_client(MandosClientWidget(server_proxy_object
427
        self.add_client(MandosClientWidget(server_proxy_object
430
                                           =self.mandos_serv,
428
                                           =self.mandos_serv,
431
                                           proxy_object
429
                                           proxy_object
432
                                           =client_proxy_object,
430
                                           =client_proxy_object,
433
                                           properties=properties,
 
 
434
                                           update_hook
431
                                           update_hook
435
                                           =self.refresh,
432
                                           =self.refresh,
436
                                           delete_hook
433
                                           delete_hook
437
                                           =self.remove_client),
434
                                           =self.remove_client,
 
 
435
                                           logger
 
 
436
                                           =self.log_message),
438
                        path=path)
437
                        path=path)
439
    
438
    
440
    def add_client(self, client, path=None):
439
    def add_client(self, client, path=None):
562
ui = UserInterface()
561
ui = UserInterface()
563
try:
562
try:
564
    ui.run()
563
    ui.run()
565
except:
564
except Exception, e:
 
 
565
    ui.log_message(unicode(e))
566
    ui.screen.stop()
566
    ui.screen.stop()
567
    raise
567
    raise
2
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
2
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
3
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
3
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
4
<!ENTITY COMMANDNAME "mandos">
4
<!ENTITY COMMANDNAME "mandos">
5
<!ENTITY TIMESTAMP "2009-09-17">
5
<!ENTITY TIMESTAMP "2009-12-09">
6
<!ENTITY % common SYSTEM "common.ent">
6
<!ENTITY % common SYSTEM "common.ent">
7
%common;
7
%common;
8
]>
8
]>
453
      backtrace.  This could be considered a feature.
453
      backtrace.  This could be considered a feature.
454
    </para>
454
    </para>
455
    <para>
455
    <para>
456
      Currently, if a client is declared <quote>invalid</quote> due to
456
      Currently, if a client is disabled due to having timed out, the
457
      having timed out, the server does not record this fact onto
457
      server does not record this fact onto permanent storage.  This
458
      permanent storage.  This has some security implications, see
458
      has some security implications, see <xref linkend="clients"/>.
459
      <xref linkend="clients"/>.
 
 
460
    </para>
459
    </para>
461
    <para>
460
    <para>
462
      There is currently no way of querying the server of the current
461
      There is currently no way of querying the server of the current
549
      </para>
548
      </para>
550
      <para>
549
      <para>
551
        If a client is compromised, its downtime should be duly noted
550
        If a client is compromised, its downtime should be duly noted
552
        by the server which would therefore declare the client
551
        by the server which would therefore disable the client.  But
553
        invalid.  But if the server was ever restarted, it would
552
        if the server was ever restarted, it would re-read its client
554
        re-read its client list from its configuration file and again
553
        list from its configuration file and again regard all clients
555
        regard all clients therein as valid, and hence eligible to
554
        therein as enabled, and hence eligible to receive their
556
        receive their passwords.  Therefore, be careful when
555
        passwords.  Therefore, be careful when restarting servers if
557
        restarting servers if it is suspected that a client has, in
556
        it is suspected that a client has, in fact, been compromised
558
        fact, been compromised by parties who may now be running a
557
        by parties who may now be running a fake Mandos client with
559
        fake Mandos client with the keys from the non-encrypted
558
        the keys from the non-encrypted initial <acronym>RAM</acronym>
560
        initial <acronym>RAM</acronym> image of the client host.  What
559
        image of the client host.  What should be done in that case
561
        should be done in that case (if restarting the server program
560
        (if restarting the server program really is necessary) is to
562
        really is necessary) is to stop the server program, edit the
561
        stop the server program, edit the configuration file to omit
563
        configuration file to omit any suspect clients, and restart
562
        any suspect clients, and restart the server program.
564
        the server program.
 
 
565
      </para>
563
      </para>
566
      <para>
564
      <para>
567
        For more details on client-side security, see
565
        For more details on client-side security, see

Loggerhead is a web-based interface for Bazaar branches