/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: 2008-09-05 07:11:24 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080905071124-9dq11jq5rfd6zfxf
* Makefile: Changed to use symbolic instead of octal modes throughout.
  (KEYDIR): New variable for the key directory.
  (install-server): Bug fix: remove "--parents" from install args.
  (install-client): Bug fix: - '' -  Also create key directory.  Do
                    not chmod plugin dir.  Create custom plugin directory
                    if not the same as normal plugin directory.  Add
                    "--dir" option to "mandos-keygen".  Add note about
                    running "mandos-keygen --password".
  (uninstall-server): Do not depend on the installed server binary,
                      since this made it impossible to do a purge
                      after an uninstall.
  (purge-client): Shred seckey.txt.  Use $(KEYDIR).

* README: Improved wording.

* initramfs-tools-hook: Use a loop to find prefix.  Also find keydir.
                        Remove "${DESTDIR}" from "copy_exec".  Do not
                        try to copy literal "*" if no custom plugins
                        are found.  Copy key files from keydir, not
                        config dir.  Only repair mode on directories
                        that actually exist.  Do not run chmod if
                        nothing needs repairing.

* plugin-runner.conf: New file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
 
14
# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
15
15
16
16
# This program is free software: you can redistribute it and/or modify
17
17
# it under the terms of the GNU General Public License as published by
30
30
# Contact the authors at <mandos@fukt.bsnet.se>.
31
31
32
32
 
33
 
from __future__ import division, with_statement, absolute_import
 
33
from __future__ import division
34
34
 
35
35
import SocketServer
36
36
import socket
 
37
import select
37
38
from optparse import OptionParser
38
39
import datetime
39
40
import errno
54
55
import stat
55
56
import logging
56
57
import logging.handlers
57
 
import pwd
58
 
from contextlib import closing
59
58
 
60
59
import dbus
61
 
import dbus.service
62
60
import gobject
63
61
import avahi
64
62
from dbus.mainloop.glib import DBusGMainLoop
65
63
import ctypes
66
 
import ctypes.util
67
64
 
68
 
version = "1.0.2"
 
65
version = "1.0"
69
66
 
70
67
logger = logging.Logger('mandos')
71
68
syslogger = logging.handlers.SysLogHandler\
83
80
class AvahiError(Exception):
84
81
    def __init__(self, value):
85
82
        self.value = value
86
 
        super(AvahiError, self).__init__()
87
83
    def __str__(self):
88
84
        return repr(self.value)
89
85
 
111
107
                  a sensible number of times
112
108
    """
113
109
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
114
 
                 servicetype = None, port = None, TXT = None, domain = "",
 
110
                 type = None, port = None, TXT = None, domain = "",
115
111
                 host = "", max_renames = 32768):
116
112
        self.interface = interface
117
113
        self.name = name
118
 
        self.type = servicetype
 
114
        self.type = type
119
115
        self.port = port
120
116
        if TXT is None:
121
117
            self.TXT = []
130
126
        if self.rename_count >= self.max_renames:
131
127
            logger.critical(u"No suitable Zeroconf service name found"
132
128
                            u" after %i retries, exiting.",
133
 
                            self.rename_count)
 
129
                            rename_count)
134
130
            raise AvahiServiceError("Too many renames")
135
131
        self.name = server.GetAlternativeServiceName(self.name)
136
132
        logger.info(u"Changing Zeroconf service name to %r ...",
172
168
# End of Avahi example code
173
169
 
174
170
 
175
 
class Client(dbus.service.Object):
 
171
class Client(object):
176
172
    """A representation of a client host served by this server.
177
173
    Attributes:
178
174
    name:      string; from the config file, used in log messages
181
177
    secret:    bytestring; sent verbatim (over TLS) to client
182
178
    host:      string; available for use by the checker command
183
179
    created:   datetime.datetime(); object creation, not client host
184
 
    started:   bool()
185
180
    last_checked_ok: datetime.datetime() or None if not yet checked OK
186
181
    timeout:   datetime.timedelta(); How long from last_checked_ok
187
182
                                     until this client is invalid
203
198
    _timeout_milliseconds: Used when calling gobject.timeout_add()
204
199
    _interval_milliseconds: - '' -
205
200
    """
206
 
    interface = u"org.mandos_system.Mandos.Clients"
207
 
    
208
 
    @dbus.service.method(interface, out_signature="s")
209
 
    def getName(self):
210
 
        "D-Bus getter method"
211
 
        return self.name
212
 
    
213
 
    @dbus.service.method(interface, out_signature="s")
214
 
    def getFingerprint(self):
215
 
        "D-Bus getter method"
216
 
        return self.fingerprint
217
 
    
218
 
    @dbus.service.method(interface, in_signature="ay",
219
 
                         byte_arrays=True)
220
 
    def setSecret(self, secret):
221
 
        "D-Bus setter method"
222
 
        self.secret = secret
223
 
    
224
201
    def _set_timeout(self, timeout):
225
 
        "Setter function for the 'timeout' attribute"
 
202
        "Setter function for 'timeout' attribute"
226
203
        self._timeout = timeout
227
204
        self._timeout_milliseconds = ((self.timeout.days
228
205
                                       * 24 * 60 * 60 * 1000)
229
206
                                      + (self.timeout.seconds * 1000)
230
207
                                      + (self.timeout.microseconds
231
208
                                         // 1000))
232
 
        # Emit D-Bus signal
233
 
        self.TimeoutChanged(self._timeout_milliseconds)
234
 
    timeout = property(lambda self: self._timeout, _set_timeout)
 
209
    timeout = property(lambda self: self._timeout,
 
210
                       _set_timeout)
235
211
    del _set_timeout
236
 
    
237
 
    @dbus.service.method(interface, out_signature="t")
238
 
    def getTimeout(self):
239
 
        "D-Bus getter method"
240
 
        return self._timeout_milliseconds
241
 
    
242
 
    @dbus.service.signal(interface, signature="t")
243
 
    def TimeoutChanged(self, t):
244
 
        "D-Bus signal"
245
 
        pass
246
 
    
247
212
    def _set_interval(self, interval):
248
 
        "Setter function for the 'interval' attribute"
 
213
        "Setter function for 'interval' attribute"
249
214
        self._interval = interval
250
215
        self._interval_milliseconds = ((self.interval.days
251
216
                                        * 24 * 60 * 60 * 1000)
253
218
                                          * 1000)
254
219
                                       + (self.interval.microseconds
255
220
                                          // 1000))
256
 
        # Emit D-Bus signal
257
 
        self.IntervalChanged(self._interval_milliseconds)
258
 
    interval = property(lambda self: self._interval, _set_interval)
 
221
    interval = property(lambda self: self._interval,
 
222
                        _set_interval)
259
223
    del _set_interval
260
 
    
261
 
    @dbus.service.method(interface, out_signature="t")
262
 
    def getInterval(self):
263
 
        "D-Bus getter method"
264
 
        return self._interval_milliseconds
265
 
    
266
 
    @dbus.service.signal(interface, signature="t")
267
 
    def IntervalChanged(self, t):
268
 
        "D-Bus signal"
269
 
        pass
270
 
    
271
 
    def __init__(self, name = None, stop_hook=None, config=None):
 
224
    def __init__(self, name = None, stop_hook=None, config={}):
272
225
        """Note: the 'checker' key in 'config' sets the
273
226
        'checker_command' attribute and *not* the 'checker'
274
227
        attribute."""
275
 
        dbus.service.Object.__init__(self, bus,
276
 
                                     "/Mandos/Clients/%s"
277
 
                                     % name.replace(".", "_"))
278
 
        if config is None:
279
 
            config = {}
280
228
        self.name = name
281
229
        logger.debug(u"Creating client %r", self.name)
282
230
        # Uppercase and remove spaces from fingerprint for later
288
236
        if "secret" in config:
289
237
            self.secret = config["secret"].decode(u"base64")
290
238
        elif "secfile" in config:
291
 
            with closing(open(os.path.expanduser
292
 
                              (os.path.expandvars
293
 
                               (config["secfile"])))) \
294
 
                               as secfile:
295
 
                self.secret = secfile.read()
 
239
            sf = open(config["secfile"])
 
240
            self.secret = sf.read()
 
241
            sf.close()
296
242
        else:
297
243
            raise TypeError(u"No secret or secfile for client %s"
298
244
                            % self.name)
299
245
        self.host = config.get("host", "")
300
246
        self.created = datetime.datetime.now()
301
 
        self.started = False
302
247
        self.last_checked_ok = None
303
248
        self.timeout = string_to_delta(config["timeout"])
304
249
        self.interval = string_to_delta(config["interval"])
308
253
        self.stop_initiator_tag = None
309
254
        self.checker_callback_tag = None
310
255
        self.check_command = config["checker"]
311
 
    
312
256
    def start(self):
313
257
        """Start this client's checker and timeout hooks"""
314
 
        self.started = True
315
258
        # Schedule a new checker to be started an 'interval' from now,
316
259
        # and every interval from then on.
317
260
        self.checker_initiator_tag = gobject.timeout_add\
323
266
        self.stop_initiator_tag = gobject.timeout_add\
324
267
                                  (self._timeout_milliseconds,
325
268
                                   self.stop)
326
 
        # Emit D-Bus signal
327
 
        self.StateChanged(True)
328
 
    
329
 
    @dbus.service.signal(interface, signature="b")
330
 
    def StateChanged(self, started):
331
 
        "D-Bus signal"
332
 
        pass
333
 
    
334
269
    def stop(self):
335
 
        """Stop this client."""
336
 
        if getattr(self, "started", False):
 
270
        """Stop this client.
 
271
        The possibility that a client might be restarted is left open,
 
272
        but not currently used."""
 
273
        # If this client doesn't have a secret, it is already stopped.
 
274
        if hasattr(self, "secret") and self.secret:
337
275
            logger.info(u"Stopping client %s", self.name)
 
276
            self.secret = None
338
277
        else:
339
278
            return False
340
279
        if getattr(self, "stop_initiator_tag", False):
346
285
        self.stop_checker()
347
286
        if self.stop_hook:
348
287
            self.stop_hook(self)
349
 
        # Emit D-Bus signal
350
 
        self.StateChanged(False)
351
288
        # Do not run this again if called by a gobject.timeout_add
352
289
        return False
353
 
    # D-Bus variant
354
 
    Stop = dbus.service.method(interface)(stop)
355
 
    
356
290
    def __del__(self):
357
291
        self.stop_hook = None
358
292
        self.stop()
359
 
    
360
293
    def checker_callback(self, pid, condition):
361
294
        """The checker has completed, so take appropriate actions."""
 
295
        now = datetime.datetime.now()
362
296
        self.checker_callback_tag = None
363
297
        self.checker = None
364
298
        if os.WIFEXITED(condition) \
365
299
               and (os.WEXITSTATUS(condition) == 0):
366
300
            logger.info(u"Checker for %(name)s succeeded",
367
301
                        vars(self))
368
 
            # Emit D-Bus signal
369
 
            self.CheckerCompleted(True)
370
 
            self.bump_timeout()
 
302
            self.last_checked_ok = now
 
303
            gobject.source_remove(self.stop_initiator_tag)
 
304
            self.stop_initiator_tag = gobject.timeout_add\
 
305
                                      (self._timeout_milliseconds,
 
306
                                       self.stop)
371
307
        elif not os.WIFEXITED(condition):
372
308
            logger.warning(u"Checker for %(name)s crashed?",
373
309
                           vars(self))
374
 
            # Emit D-Bus signal
375
 
            self.CheckerCompleted(False)
376
310
        else:
377
311
            logger.info(u"Checker for %(name)s failed",
378
312
                        vars(self))
379
 
            # Emit D-Bus signal
380
 
            self.CheckerCompleted(False)
381
 
    
382
 
    @dbus.service.signal(interface, signature="b")
383
 
    def CheckerCompleted(self, success):
384
 
        "D-Bus signal"
385
 
        pass
386
 
    
387
 
    def bump_timeout(self):
388
 
        """Bump up the timeout for this client.
389
 
        This should only be called when the client has been seen,
390
 
        alive and well.
391
 
        """
392
 
        self.last_checked_ok = datetime.datetime.now()
393
 
        gobject.source_remove(self.stop_initiator_tag)
394
 
        self.stop_initiator_tag = gobject.timeout_add\
395
 
            (self._timeout_milliseconds, self.stop)
396
 
    # D-Bus variant
397
 
    bumpTimeout = dbus.service.method(interface)(bump_timeout)
398
 
    
399
313
    def start_checker(self):
400
314
        """Start a new checker subprocess if one is not running.
401
315
        If a checker already exists, leave it running and do
436
350
                self.checker_callback_tag = gobject.child_watch_add\
437
351
                                            (self.checker.pid,
438
352
                                             self.checker_callback)
439
 
                # Emit D-Bus signal
440
 
                self.CheckerStarted(command)
441
353
            except OSError, error:
442
354
                logger.error(u"Failed to start subprocess: %s",
443
355
                             error)
444
356
        # Re-run this periodically if run by gobject.timeout_add
445
357
        return True
446
 
    
447
 
    @dbus.service.signal(interface, signature="s")
448
 
    def CheckerStarted(self, command):
449
 
        pass
450
 
    
451
 
    @dbus.service.method(interface, out_signature="b")
452
 
    def checkerIsRunning(self):
453
 
        "D-Bus getter method"
454
 
        return self.checker is not None
455
 
    
456
358
    def stop_checker(self):
457
359
        """Force the checker process, if any, to stop."""
458
360
        if self.checker_callback_tag:
470
372
            if error.errno != errno.ESRCH: # No such process
471
373
                raise
472
374
        self.checker = None
473
 
    # D-Bus variant
474
 
    StopChecker = dbus.service.method(interface)(stop_checker)
475
 
    
476
375
    def still_valid(self):
477
376
        """Has the timeout not yet passed for this client?"""
478
 
        if not self.started:
479
 
            return False
480
377
        now = datetime.datetime.now()
481
378
        if self.last_checked_ok is None:
482
379
            return now < (self.created + self.timeout)
483
380
        else:
484
381
            return now < (self.last_checked_ok + self.timeout)
485
 
    # D-Bus variant
486
 
    stillValid = dbus.service.method(interface, out_signature="b")\
487
 
        (still_valid)
488
 
    
489
 
    del interface
490
382
 
491
383
 
492
384
def peer_certificate(session):
522
414
                    (crt, ctypes.byref(datum),
523
415
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
524
416
    # Verify the self signature in the key
525
 
    crtverify = ctypes.c_uint()
 
417
    crtverify = ctypes.c_uint();
526
418
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
527
419
        (crt, 0, ctypes.byref(crtverify))
528
420
    if crtverify.value != 0:
529
421
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
530
422
        raise gnutls.errors.CertificateSecurityError("Verify failed")
531
423
    # New buffer for the fingerprint
532
 
    buf = ctypes.create_string_buffer(20)
533
 
    buf_len = ctypes.c_size_t()
 
424
    buffer = ctypes.create_string_buffer(20)
 
425
    buffer_length = ctypes.c_size_t()
534
426
    # Get the fingerprint from the certificate into the buffer
535
427
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
536
 
        (crt, ctypes.byref(buf), ctypes.byref(buf_len))
 
428
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
537
429
    # Deinit the certificate
538
430
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
539
431
    # Convert the buffer to a Python bytestring
540
 
    fpr = ctypes.string_at(buf, buf_len.value)
 
432
    fpr = ctypes.string_at(buffer, buffer_length.value)
541
433
    # Convert the bytestring to hexadecimal notation
542
434
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
543
435
    return hex_fpr
544
436
 
545
437
 
546
 
class TCP_handler(SocketServer.BaseRequestHandler, object):
 
438
class tcp_handler(SocketServer.BaseRequestHandler, object):
547
439
    """A TCP request handler class.
548
440
    Instantiated by IPv6_TCPServer for each request to handle it.
549
441
    Note: This will run in its own forked process."""
550
442
    
551
443
    def handle(self):
552
444
        logger.info(u"TCP connection from: %s",
553
 
                    unicode(self.client_address))
 
445
                     unicode(self.client_address))
554
446
        session = gnutls.connection.ClientSession\
555
447
                  (self.request, gnutls.connection.X509Credentials())
556
448
        
571
463
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
572
464
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
573
465
        #                "+DHE-DSS"))
574
 
        # Use a fallback default, since this MUST be set.
575
 
        priority = self.server.settings.get("priority", "NORMAL")
 
466
        priority = "NORMAL"             # Fallback default, since this
 
467
                                        # MUST be set.
 
468
        if self.server.settings["priority"]:
 
469
            priority = self.server.settings["priority"]
576
470
        gnutls.library.functions.gnutls_priority_set_direct\
577
 
            (session._c_object, priority, None)
 
471
            (session._c_object, priority, None);
578
472
        
579
473
        try:
580
474
            session.handshake()
608
502
                           vars(client))
609
503
            session.bye()
610
504
            return
611
 
        ## This won't work here, since we're in a fork.
612
 
        # client.bump_timeout()
613
505
        sent_size = 0
614
506
        while sent_size < len(client.secret):
615
507
            sent = session.send(client.secret[sent_size:])
620
512
        session.bye()
621
513
 
622
514
 
623
 
class IPv6_TCPServer(SocketServer.ForkingMixIn,
624
 
                     SocketServer.TCPServer, object):
 
515
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
625
516
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
626
517
    Attributes:
627
518
        settings:       Server settings
628
519
        clients:        Set() of Client objects
629
 
        enabled:        Boolean; whether this server is activated yet
630
520
    """
631
521
    address_family = socket.AF_INET6
632
522
    def __init__(self, *args, **kwargs):
636
526
        if "clients" in kwargs:
637
527
            self.clients = kwargs["clients"]
638
528
            del kwargs["clients"]
639
 
        self.enabled = False
640
 
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
 
529
        return super(type(self), self).__init__(*args, **kwargs)
641
530
    def server_bind(self):
642
531
        """This overrides the normal server_bind() function
643
532
        to bind to an interface if one was specified, and also NOT to
672
561
#                                            if_nametoindex
673
562
#                                            (self.settings
674
563
#                                             ["interface"]))
675
 
            return super(IPv6_TCPServer, self).server_bind()
676
 
    def server_activate(self):
677
 
        if self.enabled:
678
 
            return super(IPv6_TCPServer, self).server_activate()
679
 
    def enable(self):
680
 
        self.enabled = True
 
564
            return super(type(self), self).server_bind()
681
565
 
682
566
 
683
567
def string_to_delta(interval):
699
583
    timevalue = datetime.timedelta(0)
700
584
    for s in interval.split():
701
585
        try:
702
 
            suffix = unicode(s[-1])
703
 
            value = int(s[:-1])
 
586
            suffix=unicode(s[-1])
 
587
            value=int(s[:-1])
704
588
            if suffix == u"d":
705
589
                delta = datetime.timedelta(value)
706
590
            elif suffix == u"s":
746
630
    """Call the C function if_nametoindex(), or equivalent"""
747
631
    global if_nametoindex
748
632
    try:
 
633
        if "ctypes.util" not in sys.modules:
 
634
            import ctypes.util
749
635
        if_nametoindex = ctypes.cdll.LoadLibrary\
750
636
            (ctypes.util.find_library("c")).if_nametoindex
751
637
    except (OSError, AttributeError):
756
642
        def if_nametoindex(interface):
757
643
            "Get an interface index the hard way, i.e. using fcntl()"
758
644
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
759
 
            with closing(socket.socket()) as s:
760
 
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
761
 
                                    struct.pack("16s16x", interface))
 
645
            s = socket.socket()
 
646
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
647
                                struct.pack("16s16x", interface))
 
648
            s.close()
762
649
            interface_index = struct.unpack("I", ifreq[16:20])[0]
763
650
            return interface_index
764
651
    return if_nametoindex(interface)
788
675
 
789
676
 
790
677
def main():
 
678
    global main_loop_started
 
679
    main_loop_started = False
 
680
    
791
681
    parser = OptionParser(version = "%%prog %s" % version)
792
682
    parser.add_option("-i", "--interface", type="string",
793
683
                      metavar="IF", help="Bind to interface IF")
808
698
                      default="/etc/mandos", metavar="DIR",
809
699
                      help="Directory to search for configuration"
810
700
                      " files")
811
 
    options = parser.parse_args()[0]
 
701
    (options, args) = parser.parse_args()
812
702
    
813
703
    if options.check:
814
704
        import doctest
868
758
    client_config.read(os.path.join(server_settings["configdir"],
869
759
                                    "clients.conf"))
870
760
    
871
 
    clients = Set()
872
 
    tcp_server = IPv6_TCPServer((server_settings["address"],
873
 
                                 server_settings["port"]),
874
 
                                TCP_handler,
875
 
                                settings=server_settings,
876
 
                                clients=clients)
877
 
    pidfilename = "/var/run/mandos.pid"
878
 
    try:
879
 
        pidfile = open(pidfilename, "w")
880
 
    except IOError, error:
881
 
        logger.error("Could not open file %r", pidfilename)
882
 
    
883
 
    uid = 65534
884
 
    gid = 65534
885
 
    try:
886
 
        uid = pwd.getpwnam("mandos").pw_uid
887
 
    except KeyError:
888
 
        try:
889
 
            uid = pwd.getpwnam("nobody").pw_uid
890
 
        except KeyError:
891
 
            pass
892
 
    try:
893
 
        gid = pwd.getpwnam("mandos").pw_gid
894
 
    except KeyError:
895
 
        try:
896
 
            gid = pwd.getpwnam("nogroup").pw_gid
897
 
        except KeyError:
898
 
            pass
899
 
    try:
900
 
        os.setuid(uid)
901
 
        os.setgid(gid)
902
 
    except OSError, error:
903
 
        if error[0] != errno.EPERM:
904
 
            raise error
905
 
    
906
761
    global service
907
762
    service = AvahiService(name = server_settings["servicename"],
908
 
                           servicetype = "_mandos._tcp", )
 
763
                           type = "_mandos._tcp", );
909
764
    if server_settings["interface"]:
910
765
        service.interface = if_nametoindex\
911
766
                            (server_settings["interface"])
922
777
                            avahi.DBUS_INTERFACE_SERVER)
923
778
    # End of Avahi example code
924
779
    
 
780
    clients = Set()
925
781
    def remove_from_clients(client):
926
782
        clients.remove(client)
927
783
        if not clients:
949
805
        # Close all input and output, do double fork, etc.
950
806
        daemon()
951
807
    
 
808
    pidfilename = "/var/run/mandos/mandos.pid"
 
809
    pid = os.getpid()
952
810
    try:
953
 
        pid = os.getpid()
 
811
        pidfile = open(pidfilename, "w")
954
812
        pidfile.write(str(pid) + "\n")
955
813
        pidfile.close()
956
814
        del pidfile
957
 
    except IOError:
958
 
        logger.error(u"Could not write to file %r with PID %d",
959
 
                     pidfilename, pid)
960
 
    except NameError:
961
 
        # "pidfile" was never created
962
 
        pass
963
 
    del pidfilename
 
815
    except IOError, err:
 
816
        logger.error(u"Could not write %s file with PID %d",
 
817
                     pidfilename, os.getpid())
964
818
    
965
819
    def cleanup():
966
820
        "Cleanup function; run on exit"
986
840
    for client in clients:
987
841
        client.start()
988
842
    
989
 
    tcp_server.enable()
990
 
    tcp_server.server_activate()
991
 
    
 
843
    tcp_server = IPv6_TCPServer((server_settings["address"],
 
844
                                 server_settings["port"]),
 
845
                                tcp_handler,
 
846
                                settings=server_settings,
 
847
                                clients=clients)
992
848
    # Find out what port we got
993
849
    service.port = tcp_server.socket.getsockname()[1]
994
850
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
1012
868
                             (*args[2:], **kwargs) or True)
1013
869
        
1014
870
        logger.debug(u"Starting main loop")
 
871
        main_loop_started = True
1015
872
        main_loop.run()
1016
873
    except AvahiError, error:
1017
874
        logger.critical(u"AvahiError: %s" + unicode(error))