/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

Changed ForkingMixIn in favor of multiprocessing
Added approval functionallity

Show diffs side-by-side

added added

removed removed

Lines of Context:
60
60
import fcntl
61
61
import functools
62
62
import cPickle as pickle
 
63
import multiprocessing
63
64
 
64
65
import dbus
65
66
import dbus.service
97
98
                                       u' %(message)s'))
98
99
logger.addHandler(console)
99
100
 
 
101
multiprocessing_manager = multiprocessing.Manager()
 
102
 
100
103
class AvahiError(Exception):
101
104
    def __init__(self, value, *args, **kwargs):
102
105
        self.value = value
228
231
        self.server_state_changed(self.server.GetState())
229
232
 
230
233
 
 
234
# XXX Need to add:
 
235
# approved_by_default (Config option for each client)
 
236
# approved_delay (config option for each client)
 
237
# approved_duration (config option for each client)
231
238
class Client(object):
232
239
    """A representation of a client host served by this server.
233
240
    
257
264
                     runtime with vars(self) as dict, so that for
258
265
                     instance %(name)s can be used in the command.
259
266
    current_checker_command: string; current running checker_command
260
 
    changesignal: File descriptor; written to on object change
261
 
    _reset: File descriptor; for flushing changesignal
262
 
    delay: datetime.timedelta(); how long to wait for approval/secret
263
 
    approved: bool(); None if not yet approved/disapproved
 
267
    approved_delay: datetime.timedelta(); Time to wait for approval
 
268
    _approved:   bool(); 'None' if not yet approved/disapproved
 
269
    approved_duration: datetime.timedelta(); Duration of one approval
264
270
    """
265
271
    
266
272
    @staticmethod
277
283
    def interval_milliseconds(self):
278
284
        "Return the 'interval' attribute in milliseconds"
279
285
        return self._timedelta_to_milliseconds(self.interval)
 
286
 
 
287
    def approved_delay_milliseconds(self):
 
288
        return self._timedelta_to_milliseconds(self.approved_delay)
280
289
    
281
290
    def __init__(self, name = None, disable_hook=None, config=None):
282
291
        """Note: the 'checker' key in 'config' sets the
293
302
                            .replace(u" ", u""))
294
303
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
295
304
        if u"secret" in config:
296
 
            self._secret = config[u"secret"].decode(u"base64")
 
305
            self.secret = config[u"secret"].decode(u"base64")
297
306
        elif u"secfile" in config:
298
307
            with open(os.path.expanduser(os.path.expandvars
299
308
                                         (config[u"secfile"])),
300
309
                      "rb") as secfile:
301
 
                self._secret = secfile.read()
 
310
                self.secret = secfile.read()
302
311
        else:
303
312
            #XXX Need to allow secret on demand!
304
313
            raise TypeError(u"No secret or secfile for client %s"
318
327
        self.checker_command = config[u"checker"]
319
328
        self.current_checker_command = None
320
329
        self.last_connect = None
321
 
        self.changesignal, self._reset = os.pipe()
322
 
        self._approved = None #XXX should be based on configfile
323
 
        self.delay = 10; #XXX Should be based on configfile
324
 
 
325
 
    def setsecret(self, value):
326
 
        self._secret = value
327
 
        os.write(self.changesignal, "\0")
328
 
        os.read(self._reset, 1)
329
 
 
330
 
    secret = property(lambda self: self._secret, setsecret)
331
 
    del setsecret
332
 
    
333
 
    def setapproved(self, value):
334
 
        self._approved = value
335
 
        os.write(self.changesignal, "\0")
336
 
        os.read(self._reset, 1)
337
 
 
338
 
    approved = property(lambda self: self._approved, setapproved)
339
 
    del setapproved
340
 
 
 
330
        self._approved = None
 
331
        self.approved_by_default = config.get(u"approved_by_default",
 
332
                                              False)
 
333
        self.approved_delay = string_to_delta(
 
334
            config[u"approved_delay"])
 
335
        self.approved_duration = string_to_delta(
 
336
            config[u"approved_duration"])
 
337
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
 
338
 
 
339
    def send_changedstate(self):
 
340
        self.changedstate.acquire()
 
341
        self.changedstate.notify_all()
 
342
        self.changedstate.release()
 
343
        
341
344
    def enable(self):
342
345
        """Start this client's checker and timeout hooks"""
343
346
        if getattr(self, u"enabled", False):
344
347
            # Already enabled
345
348
            return
 
349
        self.send_changedstate()
346
350
        self.last_enabled = datetime.datetime.utcnow()
347
351
        # Schedule a new checker to be started an 'interval' from now,
348
352
        # and every interval from then on.
362
366
        if not getattr(self, "enabled", False):
363
367
            return False
364
368
        if not quiet:
 
369
            self.send_changedstate()
 
370
        if not quiet:
365
371
            logger.info(u"Disabling client %s", self.name)
366
372
        if getattr(self, u"disable_initiator_tag", False):
367
373
            gobject.source_remove(self.disable_initiator_tag)
500
506
                raise
501
507
        self.checker = None
502
508
 
503
 
 
504
509
def dbus_service_property(dbus_interface, signature=u"v",
505
510
                          access=u"readwrite", byte_arrays=False):
506
511
    """Decorators for marking methods of a DBusObjectWithProperties to
803
808
            self.PropertyChanged(dbus.String(u"checker_running"),
804
809
                                 dbus.Boolean(False, variant_level=1))
805
810
        return r
 
811
 
 
812
    def _reset_approved(self):
 
813
        self._approved = None
 
814
        return False
 
815
    
 
816
    def approve(self, value=True):
 
817
        self._approved = value
 
818
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
806
819
    
807
820
    ## D-Bus methods, signals & properties
808
821
    _interface = u"se.bsnet.fukt.Mandos.Client"
834
847
        pass
835
848
    
836
849
    # Rejected - signal
837
 
    @dbus.service.signal(_interface)
838
 
    def Rejected(self):
 
850
    @dbus.service.signal(_interface, signature=u"s")
 
851
    def Rejected(self, reason):
 
852
        "D-Bus signal"
 
853
        pass
 
854
    
 
855
    # NeedApproval - signal
 
856
    @dbus.service.signal(_interface, signature=u"db")
 
857
    def NeedApproval(self, timeout, default):
839
858
        "D-Bus signal"
840
859
        pass
841
860
    
842
861
    ## Methods
843
 
    
 
862
 
 
863
    # Approve - method
 
864
    @dbus.service.method(_interface, in_signature=u"b")
 
865
    def Approve(self, value):
 
866
        self.approve(value)
 
867
 
844
868
    # CheckedOK - method
845
869
    @dbus.service.method(_interface)
846
870
    def CheckedOK(self):
871
895
    
872
896
    ## Properties
873
897
    
 
898
    # xxx 3 new properties
 
899
    
874
900
    # name - property
875
901
    @dbus_service_property(_interface, signature=u"s", access=u"read")
876
902
    def name_dbus_property(self):
1010
1036
    del _interface
1011
1037
 
1012
1038
 
 
1039
class ProxyClient(object):
 
1040
    def __init__(self, child_pipe, fpr, address):
 
1041
        self._pipe = child_pipe
 
1042
        self._pipe.send(('init', fpr, address))
 
1043
        if not self._pipe.recv():
 
1044
            raise KeyError()
 
1045
 
 
1046
    def __getattribute__(self, name):
 
1047
        if(name == '_pipe'):
 
1048
            return super(ProxyClient, self).__getattribute__(name)
 
1049
        self._pipe.send(('getattr', name))
 
1050
        data = self._pipe.recv()
 
1051
        if data[0] == 'data':
 
1052
            return data[1]
 
1053
        if data[0] == 'function':
 
1054
            def func(*args, **kwargs):
 
1055
                self._pipe.send(('funcall', name, args, kwargs))
 
1056
                return self._pipe.recv()[1]
 
1057
            return func
 
1058
 
 
1059
    def __setattr__(self, name, value):
 
1060
        if(name == '_pipe'):
 
1061
            return super(ProxyClient, self).__setattr__(name, value)
 
1062
        self._pipe.send(('setattr', name, value))
 
1063
 
 
1064
 
1013
1065
class ClientHandler(socketserver.BaseRequestHandler, object):
1014
1066
    """A class to handle client connections.
1015
1067
    
1017
1069
    Note: This will run in its own forked process."""
1018
1070
    
1019
1071
    def handle(self):
1020
 
        logger.info(u"TCP connection from: %s",
1021
 
                    unicode(self.client_address))
1022
 
        logger.debug(u"IPC Pipe FD: %d",
1023
 
                     self.server.child_pipe[1].fileno())
1024
 
        # Open IPC pipe to parent process
1025
 
        with contextlib.nested(self.server.child_pipe[1],
1026
 
                               self.server.parent_pipe[0]
1027
 
                               ) as (ipc, ipc_return):
 
1072
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1073
            logger.info(u"TCP connection from: %s",
 
1074
                        unicode(self.client_address))
 
1075
            logger.debug(u"Pipe FD: %d",
 
1076
                         self.server.child_pipe.fileno())
 
1077
 
1028
1078
            session = (gnutls.connection
1029
1079
                       .ClientSession(self.request,
1030
1080
                                      gnutls.connection
1031
1081
                                      .X509Credentials()))
1032
 
            
1033
 
            line = self.request.makefile().readline()
1034
 
            logger.debug(u"Protocol version: %r", line)
1035
 
            try:
1036
 
                if int(line.strip().split()[0]) > 1:
1037
 
                    raise RuntimeError
1038
 
            except (ValueError, IndexError, RuntimeError), error:
1039
 
                logger.error(u"Unknown protocol version: %s", error)
1040
 
                return
1041
 
            
 
1082
 
1042
1083
            # Note: gnutls.connection.X509Credentials is really a
1043
1084
            # generic GnuTLS certificate credentials object so long as
1044
1085
            # no X.509 keys are added to it.  Therefore, we can use it
1045
1086
            # here despite using OpenPGP certificates.
1046
 
            
 
1087
 
1047
1088
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1048
1089
            #                      u"+AES-256-CBC", u"+SHA1",
1049
1090
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1055
1096
            (gnutls.library.functions
1056
1097
             .gnutls_priority_set_direct(session._c_object,
1057
1098
                                         priority, None))
1058
 
            
 
1099
 
 
1100
            # Start communication using the Mandos protocol
 
1101
            # Get protocol number
 
1102
            line = self.request.makefile().readline()
 
1103
            logger.debug(u"Protocol version: %r", line)
 
1104
            try:
 
1105
                if int(line.strip().split()[0]) > 1:
 
1106
                    raise RuntimeError
 
1107
            except (ValueError, IndexError, RuntimeError), error:
 
1108
                logger.error(u"Unknown protocol version: %s", error)
 
1109
                return
 
1110
 
 
1111
            # Start GnuTLS connection
1059
1112
            try:
1060
1113
                session.handshake()
1061
1114
            except gnutls.errors.GNUTLSError, error:
1073
1126
                    return
1074
1127
                logger.debug(u"Fingerprint: %s", fpr)
1075
1128
 
1076
 
                for c in self.server.clients:
1077
 
                    if c.fingerprint == fpr:
1078
 
                        client = c
1079
 
                        break
1080
 
                else:
1081
 
                    ipc.write(u"NOTFOUND %s %s\n"
1082
 
                              % (fpr, unicode(self.client_address)))
 
1129
                try:
 
1130
                    client = ProxyClient(child_pipe, fpr,
 
1131
                                         self.client_address)
 
1132
                except KeyError:
1083
1133
                    return
1084
 
 
1085
 
                class proxyclient(object):
1086
 
                    def __getattribute__(self, name):
1087
 
                        ipc.write(u"GETATTR %s %s\n" % name, client.fpr)
1088
 
                        return pickle.load(ipc_reply)
1089
 
                p = proxyclient(client)
1090
 
 
 
1134
                
 
1135
                delay = client.approved_delay
1091
1136
                while True:
1092
 
                    if not p.client.enabled:
1093
 
                        icp.write("DISABLED %s\n" % client.fpr)
1094
 
                        return
1095
 
                    if p.client.approved == False:
1096
 
                        icp.write("Disaproved")
1097
 
                        return
1098
 
                    
1099
 
                    if not p.client.secret:
1100
 
                        icp.write("No password")
1101
 
                    elif not p.client.approved:
1102
 
                        icp.write("Need approval"):
1103
 
                    else:
 
1137
                    if not client.enabled:
 
1138
                        logger.warning(u"Client %s is disabled",
 
1139
                                       client.name)
 
1140
                        if self.server.use_dbus:
 
1141
                            # Emit D-Bus signal
 
1142
                            client.Rejected("Disabled")                    
 
1143
                        return
 
1144
                    if client._approved is None:
 
1145
                        logger.info(u"Client %s need approval",
 
1146
                                    client.name)
 
1147
                        if self.server.use_dbus:
 
1148
                            # Emit D-Bus signal
 
1149
                            client.NeedApproval(
 
1150
                                client.approved_delay_milliseconds(),
 
1151
                                client.approved_by_default)
 
1152
                    elif client._approved:
1104
1153
                        #We have a password and are approved
1105
1154
                        break
1106
 
                    i, o, e = select(p.changesignal, (), (), client.delay)
1107
 
                    if not i:
1108
 
                        icp.write("Timeout passed")
 
1155
                    else:
 
1156
                        logger.warning(u"Client %s was not approved",
 
1157
                                       client.name)
 
1158
                        if self.server.use_dbus:
 
1159
                            # Emit D-Bus signal                        
 
1160
                            client.Rejected("Disapproved")
1109
1161
                        return
1110
1162
                    
1111
 
                ipc.write(u"SENDING %s\n" % client.name)
 
1163
                    #wait until timeout or approved
 
1164
                    #x = float(client._timedelta_to_milliseconds(delay))
 
1165
                    time = datetime.datetime.now()
 
1166
                    client.changedstate.acquire()
 
1167
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1168
                    client.changedstate.release()
 
1169
                    time2 = datetime.datetime.now()
 
1170
                    if (time2 - time) >= delay:
 
1171
                        if not client.approved_by_default:
 
1172
                            logger.warning("Client %s timed out while"
 
1173
                                           " waiting for approval",
 
1174
                                           client.name)
 
1175
                            if self.server.use_dbus:
 
1176
                                # Emit D-Bus signal
 
1177
                                client.Rejected("Time out")
 
1178
                            return
 
1179
                        else:
 
1180
                            break
 
1181
                    else:
 
1182
                        delay -= time2 - time
 
1183
                
1112
1184
                sent_size = 0
1113
1185
                while sent_size < len(client.secret):
 
1186
                    # XXX handle session exception
1114
1187
                    sent = session.send(client.secret[sent_size:])
1115
1188
                    logger.debug(u"Sent: %d, remaining: %d",
1116
1189
                                 sent, len(client.secret)
1117
1190
                                 - (sent_size + sent))
1118
1191
                    sent_size += sent
 
1192
 
 
1193
                logger.info(u"Sending secret to %s", client.name)
 
1194
                # bump the timeout as if seen
 
1195
                client.checked_ok()
 
1196
                if self.server.use_dbus:
 
1197
                    # Emit D-Bus signal
 
1198
                    client.GotSecret()
 
1199
 
1119
1200
            finally:
1120
1201
                session.bye()
1121
1202
    
1183
1264
        return hex_fpr
1184
1265
 
1185
1266
 
1186
 
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1187
 
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
 
1267
class MultiprocessingMixIn(object):
 
1268
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1269
    def sub_process_main(self, request, address):
 
1270
        try:
 
1271
            self.finish_request(request, address)
 
1272
        except:
 
1273
            self.handle_error(request, address)
 
1274
        self.close_request(request)
 
1275
            
 
1276
    def process_request(self, request, address):
 
1277
        """Start a new process to process the request."""
 
1278
        multiprocessing.Process(target = self.sub_process_main,
 
1279
                                args = (request, address)).start()
 
1280
 
 
1281
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1282
    """ adds a pipe to the MixIn """
1188
1283
    def process_request(self, request, client_address):
1189
1284
        """Overrides and wraps the original process_request().
1190
1285
        
1191
1286
        This function creates a new pipe in self.pipe
1192
1287
        """
1193
 
        # Child writes to child_pipe
1194
 
        self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1195
 
        # Parent writes to parent_pipe
1196
 
        self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1197
 
        super(ForkingMixInWithPipes,
 
1288
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1289
 
 
1290
        super(MultiprocessingMixInWithPipe,
1198
1291
              self).process_request(request, client_address)
1199
 
        # Close unused ends for parent
1200
 
        self.parent_pipe[0].close() # close read end
1201
 
        self.child_pipe[1].close()  # close write end
1202
 
        self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1203
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1292
        self.add_pipe(parent_pipe)
 
1293
    def add_pipe(self, parent_pipe):
1204
1294
        """Dummy function; override as necessary"""
1205
 
        child_pipe_fd.close()
1206
 
        parent_pipe_fd.close()
1207
 
 
1208
 
 
1209
 
class IPv6_TCPServer(ForkingMixInWithPipes,
 
1295
        pass
 
1296
 
 
1297
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1210
1298
                     socketserver.TCPServer, object):
1211
1299
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1212
1300
    
1297
1385
            return socketserver.TCPServer.server_activate(self)
1298
1386
    def enable(self):
1299
1387
        self.enabled = True
1300
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1388
    def add_pipe(self, parent_pipe):
1301
1389
        # Call "handle_ipc" for both data and EOF events
1302
 
        gobject.io_add_watch(child_pipe_fd.fileno(),
 
1390
        gobject.io_add_watch(parent_pipe.fileno(),
1303
1391
                             gobject.IO_IN | gobject.IO_HUP,
1304
1392
                             functools.partial(self.handle_ipc,
1305
 
                                               reply = parent_pipe_fd,
1306
 
                                               sender= child_pipe_fd))
1307
 
    def handle_ipc(self, source, condition, reply=None, sender=None):
 
1393
                                               parent_pipe = parent_pipe))
 
1394
        
 
1395
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1396
                   client_object=None):
1308
1397
        condition_names = {
1309
1398
            gobject.IO_IN: u"IN",   # There is data to read.
1310
1399
            gobject.IO_OUT: u"OUT", # Data can be written (without
1322
1411
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1323
1412
                     conditions_string)
1324
1413
        
1325
 
        # Read a line from the file object
1326
 
        cmdline = sender.readline()
1327
 
        if not cmdline:             # Empty line means end of file
1328
 
            # close the IPC pipes
1329
 
            sender.close()
1330
 
            reply.close()
1331
 
            
1332
 
            # Stop calling this function
 
1414
        # Read a request from the child
 
1415
        request = parent_pipe.recv()
 
1416
        command = request[0]
 
1417
        
 
1418
        if command == 'init':
 
1419
            fpr = request[1]
 
1420
            address = request[2]
 
1421
            
 
1422
            for c in self.clients:
 
1423
                if c.fingerprint == fpr:
 
1424
                    client = c
 
1425
                    break
 
1426
            else:
 
1427
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1428
                               u"dress: %s", fpr, address)
 
1429
                if self.use_dbus:
 
1430
                    # Emit D-Bus signal
 
1431
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1432
                parent_pipe.send(False)
 
1433
                return False
 
1434
            
 
1435
            gobject.io_add_watch(parent_pipe.fileno(),
 
1436
                                 gobject.IO_IN | gobject.IO_HUP,
 
1437
                                 functools.partial(self.handle_ipc,
 
1438
                                                   parent_pipe = parent_pipe,
 
1439
                                                   client_object = client))
 
1440
            parent_pipe.send(True)
 
1441
            # remove the old hook in favor of the new above hook on same fileno
1333
1442
            return False
1334
 
        
1335
 
        logger.debug(u"IPC command: %r", cmdline)
1336
 
        
1337
 
        # Parse and act on command
1338
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1339
 
        
1340
 
        if cmd == u"NOTFOUND":
1341
 
            fpr, address = args.split(None, 1)
1342
 
            logger.warning(u"Client not found for fingerprint: %s, ad"
1343
 
                           u"dress: %s", fpr, address)
1344
 
            if self.use_dbus:
1345
 
                # Emit D-Bus signal
1346
 
                mandos_dbus_service.ClientNotFound(fpr, address)
1347
 
        elif cmd == u"DISABLED":
1348
 
            for client in self.clients:
1349
 
                if client.name == args:
1350
 
                    logger.warning(u"Client %s is disabled", args)
1351
 
                    if self.use_dbus:
1352
 
                        # Emit D-Bus signal
1353
 
                        client.Rejected()
1354
 
                    break
1355
 
            else:
1356
 
                logger.error(u"Unknown client %s is disabled", args)
1357
 
        elif cmd == u"SENDING":
1358
 
            for client in self.clients:
1359
 
                if client.name == args:
1360
 
                    logger.info(u"Sending secret to %s", client.name)
1361
 
                    client.checked_ok()
1362
 
                    if self.use_dbus:
1363
 
                        # Emit D-Bus signal
1364
 
                        client.GotSecret()
1365
 
                    break
1366
 
            else:
1367
 
                logger.error(u"Sending secret to unknown client %s",
1368
 
                             args)
1369
 
        elif cmd == u"GETATTR":
1370
 
            attr_name, fpr = args.split(None, 1)
1371
 
            for client in self.clients:
1372
 
                if client.fingerprint == fpr:
1373
 
                    attr_value = getattr(client, attr_name, None)
1374
 
                    logger.debug("IPC reply: %r", attr_value)
1375
 
                    pickle.dump(attr_value, reply)
1376
 
                    break
1377
 
            else:
1378
 
                logger.error(u"Client %s on address %s requesting "
1379
 
                             u"attribute %s not found", fpr, address,
1380
 
                             attr_name)
1381
 
                pickle.dump(None, reply)
1382
 
        else:
1383
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1384
 
        
1385
 
        # Keep calling this function
 
1443
        if command == 'funcall':
 
1444
            funcname = request[1]
 
1445
            args = request[2]
 
1446
            kwargs = request[3]
 
1447
            
 
1448
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1449
 
 
1450
        if command == 'getattr':
 
1451
            attrname = request[1]
 
1452
            if callable(client_object.__getattribute__(attrname)):
 
1453
                parent_pipe.send(('function',))
 
1454
            else:
 
1455
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1456
 
 
1457
        if command == 'setattr':
 
1458
            attrname = request[1]
 
1459
            value = request[2]
 
1460
            setattr(client_object, attrname, value)
 
1461
            
1386
1462
        return True
1387
1463
 
1388
1464
 
1576
1652
                        u"interval": u"5m",
1577
1653
                        u"checker": u"fping -q -- %%(host)s",
1578
1654
                        u"host": u"",
 
1655
                        u"approved_delay": u"5m",
 
1656
                        u"approved_duration": u"1s",
1579
1657
                        }
1580
1658
    client_config = configparser.SafeConfigParser(client_defaults)
1581
1659
    client_config.read(os.path.join(server_settings[u"configdir"],
1658
1736
    client_class = Client
1659
1737
    if use_dbus:
1660
1738
        client_class = functools.partial(ClientDBus, bus = bus)
 
1739
    def client_config_items(config, section):
 
1740
        special_settings = {
 
1741
            "approve_by_default":
 
1742
                lambda: config.getboolean(section,
 
1743
                                          "approve_by_default"),
 
1744
            }
 
1745
        for name, value in config.items(section):
 
1746
            try:
 
1747
                yield special_settings[name]()
 
1748
            except KeyError:
 
1749
                yield (name, value)
 
1750
    
1661
1751
    tcp_server.clients.update(set(
1662
1752
            client_class(name = section,
1663
 
                         config= dict(client_config.items(section)))
 
1753
                         config= dict(client_config_items(
 
1754
                        client_config, section)))
1664
1755
            for section in client_config.sections()))
1665
1756
    if not tcp_server.clients:
1666
1757
        logger.warning(u"No clients defined")