/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-13 15:36:18 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080913153618-atp386q2bqj0ku99
* Makefile (install-client-nokey): Do "&&" instead of ";" to catch
                                   errors.

* README: Kill the straight quotes.  Add copyright notice.

* overview.xml: Improved wording.

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>.  This includes the
9
 
# following functions: "AvahiService.add", "AvahiService.remove",
10
 
# "server_state_changed", "entry_group_state_changed", and some lines
11
 
# in "main".
 
9
# methods "add" and "remove" in the "AvahiService" class, the
 
10
# "server_state_changed" and "entry_group_state_changed" functions,
 
11
# and some lines in "main".
12
12
13
13
# Everything else is
14
14
# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
24
24
#     GNU General Public License for more details.
25
25
26
26
# You should have received a copy of the GNU General Public License
27
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
27
# along with this program.  If not, see
 
28
# <http://www.gnu.org/licenses/>.
28
29
29
30
# Contact the authors at <mandos@fukt.bsnet.se>.
30
31
54
55
import stat
55
56
import logging
56
57
import logging.handlers
 
58
import pwd
57
59
 
58
60
import dbus
59
61
import gobject
61
63
from dbus.mainloop.glib import DBusGMainLoop
62
64
import ctypes
63
65
 
64
 
# Brief description of the operation of this program:
65
 
66
 
# This server announces itself as a Zeroconf service.  Connecting
67
 
# clients use the TLS protocol, with the unusual quirk that this
68
 
# server program acts as a TLS "client" while a connecting client acts
69
 
# as a TLS "server".  The client (acting as a TLS "server") must
70
 
# supply an OpenPGP certificate, and the fingerprint of this
71
 
# certificate is used by this server to look up (in a list read from a
72
 
# file at start time) which binary blob to give the client.  No other
73
 
# authentication or authorization is done by this server.
74
 
 
 
66
version = "1.0"
75
67
 
76
68
logger = logging.Logger('mandos')
77
69
syslogger = logging.handlers.SysLogHandler\
78
 
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON)
 
70
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
71
             address = "/dev/log")
79
72
syslogger.setFormatter(logging.Formatter\
80
 
                        ('%(levelname)s: %(message)s'))
 
73
                        ('Mandos: %(levelname)s: %(message)s'))
81
74
logger.addHandler(syslogger)
82
 
del syslogger
83
75
 
 
76
console = logging.StreamHandler()
 
77
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
 
78
                                       ' %(message)s'))
 
79
logger.addHandler(console)
84
80
 
85
81
class AvahiError(Exception):
86
82
    def __init__(self, value):
96
92
 
97
93
 
98
94
class AvahiService(object):
99
 
    """
 
95
    """An Avahi (Zeroconf) service.
 
96
    Attributes:
100
97
    interface: integer; avahi.IF_UNSPEC or an interface index.
101
98
               Used to optionally bind to the specified interface.
102
 
    name = string; Example: "Mandos"
103
 
    type = string; Example: "_mandos._tcp".
104
 
                   See <http://www.dns-sd.org/ServiceTypes.html>
105
 
    port = integer; what port to announce
106
 
    TXT = list of strings; TXT record for the service
107
 
    domain = string; Domain to publish on, default to .local if empty.
108
 
    host = string; Host to publish records for, default to localhost
109
 
                   if empty.
110
 
    max_renames = integer; maximum number of renames
111
 
    rename_count = integer; counter so we only rename after collisions
112
 
                   a sensible number of times
 
99
    name: string; Example: 'Mandos'
 
100
    type: string; Example: '_mandos._tcp'.
 
101
                  See <http://www.dns-sd.org/ServiceTypes.html>
 
102
    port: integer; what port to announce
 
103
    TXT: list of strings; TXT record for the service
 
104
    domain: string; Domain to publish on, default to .local if empty.
 
105
    host: string; Host to publish records for, default is localhost
 
106
    max_renames: integer; maximum number of renames
 
107
    rename_count: integer; counter so we only rename after collisions
 
108
                  a sensible number of times
113
109
    """
114
110
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
115
111
                 type = None, port = None, TXT = None, domain = "",
116
 
                 host = "", max_renames = 12):
117
 
        """An Avahi (Zeroconf) service. """
 
112
                 host = "", max_renames = 32768):
118
113
        self.interface = interface
119
114
        self.name = name
120
115
        self.type = type
126
121
        self.domain = domain
127
122
        self.host = host
128
123
        self.rename_count = 0
 
124
        self.max_renames = max_renames
129
125
    def rename(self):
130
126
        """Derived from the Avahi example code"""
131
127
        if self.rename_count >= self.max_renames:
132
 
            logger.critical(u"No suitable service name found after %i"
133
 
                            u" retries, exiting.", rename_count)
 
128
            logger.critical(u"No suitable Zeroconf service name found"
 
129
                            u" after %i retries, exiting.",
 
130
                            rename_count)
134
131
            raise AvahiServiceError("Too many renames")
135
 
        name = server.GetAlternativeServiceName(name)
136
 
        logger.error(u"Changing name to %r ...", name)
 
132
        self.name = server.GetAlternativeServiceName(self.name)
 
133
        logger.info(u"Changing Zeroconf service name to %r ...",
 
134
                    str(self.name))
 
135
        syslogger.setFormatter(logging.Formatter\
 
136
                               ('Mandos (%s): %%(levelname)s:'
 
137
                               ' %%(message)s' % self.name))
137
138
        self.remove()
138
139
        self.add()
139
140
        self.rename_count += 1
151
152
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
152
153
            group.connect_to_signal('StateChanged',
153
154
                                    entry_group_state_changed)
154
 
        logger.debug(u"Adding service '%s' of type '%s' ...",
 
155
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
155
156
                     service.name, service.type)
156
157
        group.AddService(
157
158
                self.interface,         # interface
175
176
    fingerprint: string (40 or 32 hexadecimal digits); used to
176
177
                 uniquely identify the client
177
178
    secret:    bytestring; sent verbatim (over TLS) to client
178
 
    fqdn:      string (FQDN); available for use by the checker command
 
179
    host:      string; available for use by the checker command
179
180
    created:   datetime.datetime(); object creation, not client host
180
181
    last_checked_ok: datetime.datetime() or None if not yet checked OK
181
182
    timeout:   datetime.timedelta(); How long from last_checked_ok
222
223
                        _set_interval)
223
224
    del _set_interval
224
225
    def __init__(self, name = None, stop_hook=None, config={}):
225
 
        """Note: the 'checker' argument sets the 'checker_command'
226
 
        attribute and not the 'checker' attribute.."""
 
226
        """Note: the 'checker' key in 'config' sets the
 
227
        'checker_command' attribute and *not* the 'checker'
 
228
        attribute."""
227
229
        self.name = name
228
230
        logger.debug(u"Creating client %r", self.name)
229
 
        # Uppercase and remove spaces from fingerprint
230
 
        # for later comparison purposes with return value of
231
 
        # the fingerprint() function
 
231
        # Uppercase and remove spaces from fingerprint for later
 
232
        # comparison purposes with return value from the fingerprint()
 
233
        # function
232
234
        self.fingerprint = config["fingerprint"].upper()\
233
235
                           .replace(u" ", u"")
234
236
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
241
243
        else:
242
244
            raise TypeError(u"No secret or secfile for client %s"
243
245
                            % self.name)
244
 
        self.fqdn = config.get("fqdn", "")
 
246
        self.host = config.get("host", "")
245
247
        self.created = datetime.datetime.now()
246
248
        self.last_checked_ok = None
247
249
        self.timeout = string_to_delta(config["timeout"])
270
272
        The possibility that a client might be restarted is left open,
271
273
        but not currently used."""
272
274
        # If this client doesn't have a secret, it is already stopped.
273
 
        if self.secret:
 
275
        if hasattr(self, "secret") and self.secret:
274
276
            logger.info(u"Stopping client %s", self.name)
275
277
            self.secret = None
276
278
        else:
324
326
        if self.checker is None:
325
327
            try:
326
328
                # In case check_command has exactly one % operator
327
 
                command = self.check_command % self.fqdn
 
329
                command = self.check_command % self.host
328
330
            except TypeError:
329
331
                # Escape attributes for the shell
330
332
                escaped_attrs = dict((key, re.escape(str(val)))
339
341
            try:
340
342
                logger.info(u"Starting checker %r for %s",
341
343
                            command, self.name)
 
344
                # We don't need to redirect stdout and stderr, since
 
345
                # in normal mode, that is already done by daemon(),
 
346
                # and in debug mode we don't want to.  (Stdin is
 
347
                # always replaced by /dev/null.)
342
348
                self.checker = subprocess.Popen(command,
343
349
                                                close_fds=True,
344
350
                                                shell=True, cwd="/")
345
351
                self.checker_callback_tag = gobject.child_watch_add\
346
352
                                            (self.checker.pid,
347
353
                                             self.checker_callback)
348
 
            except subprocess.OSError, error:
 
354
            except OSError, error:
349
355
                logger.error(u"Failed to start subprocess: %s",
350
356
                             error)
351
357
        # Re-run this periodically if run by gobject.timeout_add
357
363
            self.checker_callback_tag = None
358
364
        if getattr(self, "checker", None) is None:
359
365
            return
360
 
        logger.debug("Stopping checker for %(name)s", vars(self))
 
366
        logger.debug(u"Stopping checker for %(name)s", vars(self))
361
367
        try:
362
368
            os.kill(self.checker.pid, signal.SIGTERM)
363
369
            #os.sleep(0.5)
395
401
 
396
402
def fingerprint(openpgp):
397
403
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
398
 
    # New empty GnuTLS certificate
399
 
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
400
 
    gnutls.library.functions.gnutls_openpgp_crt_init\
401
 
        (ctypes.byref(crt))
402
404
    # New GnuTLS "datum" with the OpenPGP public key
403
405
    datum = gnutls.library.types.gnutls_datum_t\
404
406
        (ctypes.cast(ctypes.c_char_p(openpgp),
405
407
                     ctypes.POINTER(ctypes.c_ubyte)),
406
408
         ctypes.c_uint(len(openpgp)))
 
409
    # New empty GnuTLS certificate
 
410
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
411
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
412
        (ctypes.byref(crt))
407
413
    # Import the OpenPGP public key into the certificate
408
 
    ret = gnutls.library.functions.gnutls_openpgp_crt_import\
409
 
        (crt,
410
 
         ctypes.byref(datum),
411
 
         gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
414
    gnutls.library.functions.gnutls_openpgp_crt_import\
 
415
                    (crt, ctypes.byref(datum),
 
416
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
417
    # Verify the self signature in the key
 
418
    crtverify = ctypes.c_uint();
 
419
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
 
420
        (crt, 0, ctypes.byref(crtverify))
 
421
    if crtverify.value != 0:
 
422
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
423
        raise gnutls.errors.CertificateSecurityError("Verify failed")
412
424
    # New buffer for the fingerprint
413
425
    buffer = ctypes.create_string_buffer(20)
414
426
    buffer_length = ctypes.c_size_t()
506
518
    Attributes:
507
519
        settings:       Server settings
508
520
        clients:        Set() of Client objects
 
521
        enabled:        Boolean; whether this server is activated yet
509
522
    """
510
523
    address_family = socket.AF_INET6
511
524
    def __init__(self, *args, **kwargs):
515
528
        if "clients" in kwargs:
516
529
            self.clients = kwargs["clients"]
517
530
            del kwargs["clients"]
 
531
        self.enabled = False
518
532
        return super(type(self), self).__init__(*args, **kwargs)
519
533
    def server_bind(self):
520
534
        """This overrides the normal server_bind() function
540
554
                in6addr_any = "::"
541
555
                self.server_address = (in6addr_any,
542
556
                                       self.server_address[1])
543
 
            elif self.server_address[1] is None:
 
557
            elif not self.server_address[1]:
544
558
                self.server_address = (self.server_address[0],
545
559
                                       0)
 
560
#                 if self.settings["interface"]:
 
561
#                     self.server_address = (self.server_address[0],
 
562
#                                            0, # port
 
563
#                                            0, # flowinfo
 
564
#                                            if_nametoindex
 
565
#                                            (self.settings
 
566
#                                             ["interface"]))
546
567
            return super(type(self), self).server_bind()
 
568
    def server_activate(self):
 
569
        if self.enabled:
 
570
            return super(type(self), self).server_activate()
 
571
    def enable(self):
 
572
        self.enabled = True
547
573
 
548
574
 
549
575
def string_to_delta(interval):
559
585
    datetime.timedelta(1)
560
586
    >>> string_to_delta(u'1w')
561
587
    datetime.timedelta(7)
 
588
    >>> string_to_delta('5m 30s')
 
589
    datetime.timedelta(0, 330)
562
590
    """
563
 
    try:
564
 
        suffix=unicode(interval[-1])
565
 
        value=int(interval[:-1])
566
 
        if suffix == u"d":
567
 
            delta = datetime.timedelta(value)
568
 
        elif suffix == u"s":
569
 
            delta = datetime.timedelta(0, value)
570
 
        elif suffix == u"m":
571
 
            delta = datetime.timedelta(0, 0, 0, 0, value)
572
 
        elif suffix == u"h":
573
 
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
574
 
        elif suffix == u"w":
575
 
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
576
 
        else:
 
591
    timevalue = datetime.timedelta(0)
 
592
    for s in interval.split():
 
593
        try:
 
594
            suffix=unicode(s[-1])
 
595
            value=int(s[:-1])
 
596
            if suffix == u"d":
 
597
                delta = datetime.timedelta(value)
 
598
            elif suffix == u"s":
 
599
                delta = datetime.timedelta(0, value)
 
600
            elif suffix == u"m":
 
601
                delta = datetime.timedelta(0, 0, 0, 0, value)
 
602
            elif suffix == u"h":
 
603
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
 
604
            elif suffix == u"w":
 
605
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
 
606
            else:
 
607
                raise ValueError
 
608
        except (ValueError, IndexError):
577
609
            raise ValueError
578
 
    except (ValueError, IndexError):
579
 
        raise ValueError
580
 
    return delta
 
610
        timevalue += delta
 
611
    return timevalue
581
612
 
582
613
 
583
614
def server_state_changed(state):
584
615
    """Derived from the Avahi example code"""
585
616
    if state == avahi.SERVER_COLLISION:
586
 
        logger.error(u"Server name collision")
 
617
        logger.error(u"Zeroconf server name collision")
587
618
        service.remove()
588
619
    elif state == avahi.SERVER_RUNNING:
589
620
        service.add()
591
622
 
592
623
def entry_group_state_changed(state, error):
593
624
    """Derived from the Avahi example code"""
594
 
    logger.debug(u"state change: %i", state)
 
625
    logger.debug(u"Avahi state change: %i", state)
595
626
    
596
627
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
597
 
        logger.debug(u"Service established.")
 
628
        logger.debug(u"Zeroconf service established.")
598
629
    elif state == avahi.ENTRY_GROUP_COLLISION:
599
 
        logger.warning(u"Service name collision.")
 
630
        logger.warning(u"Zeroconf service name collision.")
600
631
        service.rename()
601
632
    elif state == avahi.ENTRY_GROUP_FAILURE:
602
 
        logger.critical(u"Error in group state changed %s",
 
633
        logger.critical(u"Avahi: Error in group state changed %s",
603
634
                        unicode(error))
604
635
        raise AvahiGroupError("State changed: %s", str(error))
605
636
 
628
659
    return if_nametoindex(interface)
629
660
 
630
661
 
631
 
def daemon(nochdir, noclose):
 
662
def daemon(nochdir = False, noclose = False):
632
663
    """See daemon(3).  Standard BSD Unix function.
633
664
    This should really exist as os.daemon, but it doesn't (yet)."""
634
665
    if os.fork():
636
667
    os.setsid()
637
668
    if not nochdir:
638
669
        os.chdir("/")
 
670
    if os.fork():
 
671
        sys.exit()
639
672
    if not noclose:
640
673
        # Close all standard open file descriptors
641
674
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
653
686
    global main_loop_started
654
687
    main_loop_started = False
655
688
    
656
 
    parser = OptionParser()
 
689
    parser = OptionParser(version = "%%prog %s" % version)
657
690
    parser.add_option("-i", "--interface", type="string",
658
691
                      metavar="IF", help="Bind to interface IF")
659
692
    parser.add_option("-a", "--address", type="string",
662
695
                      help="Port number to receive requests on")
663
696
    parser.add_option("--check", action="store_true", default=False,
664
697
                      help="Run self-test")
665
 
    parser.add_option("--debug", action="store_true", default=False,
 
698
    parser.add_option("--debug", action="store_true",
666
699
                      help="Debug mode; run in foreground and log to"
667
700
                      " terminal")
668
701
    parser.add_option("--priority", type="string", help="GnuTLS"
693
726
    # Parse config file for server-global settings
694
727
    server_config = ConfigParser.SafeConfigParser(server_defaults)
695
728
    del server_defaults
696
 
    server_config.read(os.path.join(options.configdir, "server.conf"))
697
 
    server_section = "server"
 
729
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
698
730
    # Convert the SafeConfigParser object to a dict
699
 
    server_settings = dict(server_config.items(server_section))
 
731
    server_settings = server_config.defaults()
700
732
    # Use getboolean on the boolean config option
701
733
    server_settings["debug"] = server_config.getboolean\
702
 
                               (server_section, "debug")
 
734
                               ("DEFAULT", "debug")
703
735
    del server_config
704
736
    
705
737
    # Override the settings from the config file with command line
712
744
    del options
713
745
    # Now we have our good server settings in "server_settings"
714
746
    
 
747
    debug = server_settings["debug"]
 
748
    
 
749
    if not debug:
 
750
        syslogger.setLevel(logging.WARNING)
 
751
        console.setLevel(logging.WARNING)
 
752
    
 
753
    if server_settings["servicename"] != "Mandos":
 
754
        syslogger.setFormatter(logging.Formatter\
 
755
                               ('Mandos (%s): %%(levelname)s:'
 
756
                                ' %%(message)s'
 
757
                                % server_settings["servicename"]))
 
758
    
715
759
    # Parse config file with clients
716
760
    client_defaults = { "timeout": "1h",
717
761
                        "interval": "5m",
718
 
                        "checker": "fping -q -- %%(fqdn)s",
 
762
                        "checker": "fping -q -- %(host)s",
 
763
                        "host": "",
719
764
                        }
720
765
    client_config = ConfigParser.SafeConfigParser(client_defaults)
721
766
    client_config.read(os.path.join(server_settings["configdir"],
722
767
                                    "clients.conf"))
723
768
    
 
769
    clients = Set()
 
770
    tcp_server = IPv6_TCPServer((server_settings["address"],
 
771
                                 server_settings["port"]),
 
772
                                tcp_handler,
 
773
                                settings=server_settings,
 
774
                                clients=clients)
 
775
    pidfilename = "/var/run/mandos.pid"
 
776
    try:
 
777
        pidfile = open(pidfilename, "w")
 
778
    except IOError, error:
 
779
        logger.error("Could not open file %r", pidfilename)
 
780
    
 
781
    uid = 65534
 
782
    gid = 65534
 
783
    try:
 
784
        uid = pwd.getpwnam("mandos").pw_uid
 
785
    except KeyError:
 
786
        try:
 
787
            uid = pwd.getpwnam("nobody").pw_uid
 
788
        except KeyError:
 
789
            pass
 
790
    try:
 
791
        gid = pwd.getpwnam("mandos").pw_gid
 
792
    except KeyError:
 
793
        try:
 
794
            gid = pwd.getpwnam("nogroup").pw_gid
 
795
        except KeyError:
 
796
            pass
 
797
    try:
 
798
        os.setuid(uid)
 
799
        os.setgid(gid)
 
800
    except OSError, error:
 
801
        if error[0] != errno.EPERM:
 
802
            raise error
 
803
    
724
804
    global service
725
805
    service = AvahiService(name = server_settings["servicename"],
726
806
                           type = "_mandos._tcp", );
727
807
    if server_settings["interface"]:
728
 
        service.interface = if_nametoindex(server_settings["interface"])
 
808
        service.interface = if_nametoindex\
 
809
                            (server_settings["interface"])
729
810
    
730
811
    global main_loop
731
812
    global bus
734
815
    DBusGMainLoop(set_as_default=True )
735
816
    main_loop = gobject.MainLoop()
736
817
    bus = dbus.SystemBus()
737
 
    server = dbus.Interface(
738
 
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
739
 
            avahi.DBUS_INTERFACE_SERVER )
 
818
    server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
 
819
                                           avahi.DBUS_PATH_SERVER),
 
820
                            avahi.DBUS_INTERFACE_SERVER)
740
821
    # End of Avahi example code
741
822
    
742
 
    debug = server_settings["debug"]
743
 
    
744
 
    if debug:
745
 
        console = logging.StreamHandler()
746
 
        # console.setLevel(logging.DEBUG)
747
 
        console.setFormatter(logging.Formatter\
748
 
                             ('%(levelname)s: %(message)s'))
749
 
        logger.addHandler(console)
750
 
        del console
751
 
    
752
 
    clients = Set()
753
823
    def remove_from_clients(client):
754
824
        clients.remove(client)
755
825
        if not clients:
761
831
                              config
762
832
                              = dict(client_config.items(section)))
763
833
                       for section in client_config.sections()))
764
 
    
765
 
    if not debug:
766
 
        daemon(False, False)
 
834
    if not clients:
 
835
        logger.critical(u"No clients defined")
 
836
        sys.exit(1)
 
837
    
 
838
    if debug:
 
839
        # Redirect stdin so all checkers get /dev/null
 
840
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
841
        os.dup2(null, sys.stdin.fileno())
 
842
        if null > 2:
 
843
            os.close(null)
 
844
    else:
 
845
        # No console logging
 
846
        logger.removeHandler(console)
 
847
        # Close all input and output, do double fork, etc.
 
848
        daemon()
 
849
    
 
850
    try:
 
851
        pid = os.getpid()
 
852
        pidfile.write(str(pid) + "\n")
 
853
        pidfile.close()
 
854
        del pidfile
 
855
    except IOError, err:
 
856
        logger.error(u"Could not write to file %r with PID %d",
 
857
                     pidfilename, pid)
 
858
    except NameError:
 
859
        # "pidfile" was never created
 
860
        pass
 
861
    del pidfilename
767
862
    
768
863
    def cleanup():
769
864
        "Cleanup function; run on exit"
789
884
    for client in clients:
790
885
        client.start()
791
886
    
792
 
    tcp_server = IPv6_TCPServer((server_settings["address"],
793
 
                                 server_settings["port"]),
794
 
                                tcp_handler,
795
 
                                settings=server_settings,
796
 
                                clients=clients)
 
887
    tcp_server.enable()
 
888
    tcp_server.server_activate()
 
889
    
797
890
    # Find out what port we got
798
891
    service.port = tcp_server.socket.getsockname()[1]
799
892
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
816
909
                             tcp_server.handle_request\
817
910
                             (*args[2:], **kwargs) or True)
818
911
        
819
 
        logger.debug("Starting main loop")
 
912
        logger.debug(u"Starting main loop")
820
913
        main_loop_started = True
821
914
        main_loop.run()
822
915
    except AvahiError, error: