/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 server.py

  • Committer: Teddy Hogeborn
  • Date: 2008-06-30 01:43:39 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080630014339-rsuztydpl2w5ml83
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
             old broadcast-UDP-to-port-49001 method.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
2
 
 
3
from __future__ import division
 
4
 
3
5
import SocketServer
4
6
import socket
5
7
import select
11
13
import gnutls.errors
12
14
import ConfigParser
13
15
import sys
 
16
import re
 
17
import os
 
18
import signal
 
19
from sets import Set
 
20
import subprocess
 
21
 
 
22
import dbus
 
23
import gobject
 
24
import avahi
 
25
from dbus.mainloop.glib import DBusGMainLoop
 
26
 
 
27
# This variable is used to optionally bind to a specified
 
28
# interface.
 
29
serviceInterface = avahi.IF_UNSPEC
 
30
# It is a global variable to fit in with the rest of the
 
31
# variables from the Avahi server example code:
 
32
serviceName = "Mandos"
 
33
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
 
34
servicePort = None                      # Not known at startup
 
35
serviceTXT = []                         # TXT record for the service
 
36
domain = ""                  # Domain to publish on, default to .local
 
37
host = ""          # Host to publish records for, default to localhost
 
38
group = None #our entry group
 
39
rename_count = 12       # Counter so we only rename after collisions a
 
40
                        # sensible number of times
 
41
# End of Avahi example code
14
42
 
15
43
 
16
44
class Client(object):
17
 
    def __init__(self, name=None, options=None, dn=None,
18
 
                 password=None, passfile=None, fqdn=None,
 
45
    """A representation of a client host served by this server.
 
46
    Attributes:
 
47
    password:  string
 
48
    fqdn:      string, FQDN (used by the checker)
 
49
    created:   datetime.datetime()
 
50
    last_seen: datetime.datetime() or None if not yet seen
 
51
    timeout:   datetime.timedelta(); How long from last_seen until
 
52
                                     this client is invalid
 
53
    interval:  datetime.timedelta(); How often to start a new checker
 
54
    timeout_milliseconds: Used by gobject.timeout_add()
 
55
    interval_milliseconds: - '' -
 
56
    stop_hook: If set, called by stop() as stop_hook(self)
 
57
    checker:   subprocess.Popen(); a running checker process used
 
58
                                   to see if the client lives.
 
59
                                   Is None if no process is running.
 
60
    checker_initiator_tag: a gobject event source tag, or None
 
61
    stop_initiator_tag:    - '' -
 
62
    checker_callback_tag:  - '' -
 
63
    """
 
64
    def __init__(self, name=None, options=None, stop_hook=None,
 
65
                 dn=None, password=None, passfile=None, fqdn=None,
19
66
                 timeout=None, interval=-1):
20
67
        self.name = name
21
68
        self.dn = dn
24
71
        elif passfile:
25
72
            self.password = open(passfile).readall()
26
73
        else:
27
 
            print "No Password or Passfile in client config file"
28
 
            # raise RuntimeError XXX
29
 
            self.password = "gazonk"
30
 
        self.fqdn = fqdn
 
74
            raise RuntimeError(u"No Password or Passfile for client %s"
 
75
                               % self.name)
 
76
        self.fqdn = fqdn                # string
31
77
        self.created = datetime.datetime.now()
32
78
        self.last_seen = None
33
79
        if timeout is None:
34
80
            timeout = options.timeout
35
81
        self.timeout = timeout
 
82
        self.timeout_milliseconds = ((self.timeout.days
 
83
                                      * 24 * 60 * 60 * 1000)
 
84
                                     + (self.timeout.seconds * 1000)
 
85
                                     + (self.timeout.microseconds
 
86
                                        // 1000))
36
87
        if interval == -1:
37
88
            interval = options.interval
 
89
        else:
 
90
            interval = string_to_delta(interval)
38
91
        self.interval = interval
39
 
        self.next_check = datetime.datetime.now()
40
 
 
41
 
 
42
 
class server_metaclass(type):
43
 
    "Common behavior for the UDP and TCP server classes"
44
 
    def __new__(cls, name, bases, attrs):
45
 
        attrs["address_family"] = socket.AF_INET6
46
 
        attrs["allow_reuse_address"] = True
47
 
        def server_bind(self):
48
 
            if self.options.interface:
49
 
                if not hasattr(socket, "SO_BINDTODEVICE"):
50
 
                    # From /usr/include/asm-i486/socket.h
51
 
                    socket.SO_BINDTODEVICE = 25
52
 
                try:
53
 
                    self.socket.setsockopt(socket.SOL_SOCKET,
54
 
                                           socket.SO_BINDTODEVICE,
55
 
                                           self.options.interface)
56
 
                except socket.error, error:
57
 
                    if error[0] == errno.EPERM:
58
 
                        print "Warning: No permission to bind to interface", \
59
 
                              self.options.interface
60
 
                    else:
61
 
                        raise error
62
 
            return super(type(self), self).server_bind()
63
 
        attrs["server_bind"] = server_bind
64
 
        def init(self, *args, **kwargs):
65
 
            if "options" in kwargs:
66
 
                self.options = kwargs["options"]
67
 
                del kwargs["options"]
68
 
            if "clients" in kwargs:
69
 
                self.clients = kwargs["clients"]
70
 
                del kwargs["clients"]
71
 
            if "credentials" in kwargs:
72
 
                self.credentials = kwargs["credentials"]
73
 
                del kwargs["credentials"]
74
 
            return super(type(self), self).__init__(*args, **kwargs)
75
 
        attrs["__init__"] = init
76
 
        return type.__new__(cls, name, bases, attrs)
77
 
 
78
 
 
79
 
class udp_handler(SocketServer.DatagramRequestHandler, object):
80
 
    def handle(self):
81
 
        self.wfile.write("Polo")
82
 
        print "UDP request answered"
83
 
 
84
 
 
85
 
class IPv6_UDPServer(SocketServer.UDPServer, object):
86
 
    __metaclass__ = server_metaclass
87
 
    def verify_request(self, request, client_address):
88
 
        print "UDP request came"
89
 
        return request[0] == "Marco"
 
92
        self.interval_milliseconds = ((self.interval.days
 
93
                                       * 24 * 60 * 60 * 1000)
 
94
                                      + (self.interval.seconds * 1000)
 
95
                                      + (self.interval.microseconds
 
96
                                         // 1000))
 
97
        self.stop_hook = stop_hook
 
98
        self.checker = None
 
99
        self.checker_initiator_tag = None
 
100
        self.stop_initiator_tag = None
 
101
        self.checker_callback_tag = None
 
102
    def start(self):
 
103
        """Start this clients checker and timeout hooks"""
 
104
        # Schedule a new checker to be started an 'interval' from now,
 
105
        # and every interval from then on.
 
106
        self.checker_initiator_tag = gobject.\
 
107
                                     timeout_add(self.interval_milliseconds,
 
108
                                                 self.start_checker)
 
109
        # Also start a new checker *right now*.
 
110
        self.start_checker()
 
111
        # Schedule a stop() when 'timeout' has passed
 
112
        self.stop_initiator_tag = gobject.\
 
113
                                     timeout_add(self.timeout_milliseconds,
 
114
                                                 self.stop)
 
115
    def stop(self):
 
116
        """Stop this client.
 
117
        The possibility that this client might be restarted is left
 
118
        open, but not currently used."""
 
119
        # print "Stopping client", self.name
 
120
        self.password = None
 
121
        if self.stop_initiator_tag:
 
122
            gobject.source_remove(self.stop_initiator_tag)
 
123
            self.stop_initiator_tag = None
 
124
        if self.checker_initiator_tag:
 
125
            gobject.source_remove(self.checker_initiator_tag)
 
126
            self.checker_initiator_tag = None
 
127
        self.stop_checker()
 
128
        if self.stop_hook:
 
129
            self.stop_hook(self)
 
130
        # Do not run this again if called by a gobject.timeout_add
 
131
        return False
 
132
    def __del__(self):
 
133
        # Some code duplication here and in stop()
 
134
        if hasattr(self, "stop_initiator_tag") \
 
135
               and self.stop_initiator_tag:
 
136
            gobject.source_remove(self.stop_initiator_tag)
 
137
            self.stop_initiator_tag = None
 
138
        if hasattr(self, "checker_initiator_tag") \
 
139
               and self.checker_initiator_tag:
 
140
            gobject.source_remove(self.checker_initiator_tag)
 
141
            self.checker_initiator_tag = None
 
142
        self.stop_checker()
 
143
    def checker_callback(self, pid, condition):
 
144
        """The checker has completed, so take appropriate actions."""
 
145
        now = datetime.datetime.now()
 
146
        if os.WIFEXITED(condition) \
 
147
               and (os.WEXITSTATUS(condition) == 0):
 
148
            #print "Checker for %(name)s succeeded" % vars(self)
 
149
            self.last_seen = now
 
150
            gobject.source_remove(self.stop_initiator_tag)
 
151
            self.stop_initiator_tag = gobject.\
 
152
                                      timeout_add(self.timeout_milliseconds,
 
153
                                                  self.stop)
 
154
        #else:
 
155
        #    if not os.WIFEXITED(condition):
 
156
        #        print "Checker for %(name)s crashed?" % vars(self)
 
157
        #    else:
 
158
        #        print "Checker for %(name)s failed" % vars(self)
 
159
        self.checker = None
 
160
        self.checker_callback_tag = None
 
161
    def start_checker(self):
 
162
        """Start a new checker subprocess if one is not running.
 
163
        If a checker already exists, leave it running and do
 
164
        nothing."""
 
165
        if self.checker is None:
 
166
            #print "Starting checker for", self.name
 
167
            try:
 
168
                self.checker = subprocess.\
 
169
                               Popen("sleep 1; fping -q -- %s"
 
170
                                     % re.escape(self.fqdn),
 
171
                                     stdout=subprocess.PIPE,
 
172
                                     close_fds=True, shell=True,
 
173
                                     cwd="/")
 
174
                self.checker_callback_tag = gobject.\
 
175
                                            child_watch_add(self.checker.pid,
 
176
                                                            self.\
 
177
                                                            checker_callback)
 
178
            except subprocess.OSError, error:
 
179
                sys.stderr.write(u"Failed to start subprocess: %s\n"
 
180
                                 % error)
 
181
        # Re-run this periodically if run by gobject.timeout_add
 
182
        return True
 
183
    def stop_checker(self):
 
184
        """Force the checker process, if any, to stop."""
 
185
        if not hasattr(self, "checker") or self.checker is None:
 
186
            return
 
187
        gobject.source_remove(self.checker_callback_tag)
 
188
        self.checker_callback_tag = None
 
189
        os.kill(self.checker.pid, signal.SIGTERM)
 
190
        if self.checker.poll() is None:
 
191
            os.kill(self.checker.pid, signal.SIGKILL)
 
192
        self.checker = None
 
193
    def still_valid(self, now=None):
 
194
        """Has the timeout not yet passed for this client?"""
 
195
        if now is None:
 
196
            now = datetime.datetime.now()
 
197
        if self.last_seen is None:
 
198
            return now < (self.created + self.timeout)
 
199
        else:
 
200
            return now < (self.last_seen + self.timeout)
90
201
 
91
202
 
92
203
class tcp_handler(SocketServer.BaseRequestHandler, object):
 
204
    """A TCP request handler class.
 
205
    Instantiated by IPv6_TCPServer for each request to handle it.
 
206
    Note: This will run in its own forked process."""
93
207
    def handle(self):
94
 
        print "TCP request came"
95
 
        print "Request:", self.request
96
 
        print "Client Address:", self.client_address
97
 
        print "Server:", self.server
 
208
        #print u"TCP request came"
 
209
        #print u"Request:", self.request
 
210
        #print u"Client Address:", self.client_address
 
211
        #print u"Server:", self.server
98
212
        session = gnutls.connection.ServerSession(self.request,
99
 
                                                  self.server.credentials)
100
 
        session.handshake()
101
 
        if session.peer_certificate:
102
 
            print "DN:", session.peer_certificate.subject
 
213
                                                  self.server\
 
214
                                                  .credentials)
 
215
        try:
 
216
            session.handshake()
 
217
        except gnutls.errors.GNUTLSError, error:
 
218
            #sys.stderr.write(u"Handshake failed: %s\n" % error)
 
219
            # Do not run session.bye() here: the session is not
 
220
            # established.  Just abandon the request.
 
221
            return
 
222
        #if session.peer_certificate:
 
223
        #    print "DN:", session.peer_certificate.subject
103
224
        try:
104
225
            session.verify_peer()
105
226
        except gnutls.errors.CertificateError, error:
106
 
            print "Verify failed", error
 
227
            #sys.stderr.write(u"Verify failed: %s\n" % error)
107
228
            session.bye()
108
229
            return
109
 
        try:
110
 
            session.send(dict((client.dn, client.password)
111
 
                              for client in self.server.clients)
112
 
                         [session.peer_certificate.subject])
113
 
        except KeyError:
114
 
            session.send("gazonk")
115
 
            # Log maybe? XXX
 
230
        client = None
 
231
        for c in clients:
 
232
            if c.dn == session.peer_certificate.subject:
 
233
                client = c
 
234
                break
 
235
        # Have to check if client.still_valid(), since it is possible
 
236
        # that the client timed out while establishing the GnuTLS
 
237
        # session.
 
238
        if client and client.still_valid():
 
239
            session.send(client.password)
 
240
        else:
 
241
            #if client:
 
242
            #    sys.stderr.write(u"Client %(name)s is invalid\n"
 
243
            #                     % vars(client))
 
244
            #else:
 
245
            #    sys.stderr.write(u"Client not found for DN: %s\n"
 
246
            #                     % session.peer_certificate.subject)
 
247
            #session.send("gazonk")
 
248
            pass
116
249
        session.bye()
117
250
 
118
251
 
119
252
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
120
 
    __metaclass__ = server_metaclass
121
 
    request_queue_size = 1024
122
 
 
123
 
 
124
 
in6addr_any = "::"
125
 
 
126
 
cred = None
 
253
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
254
    Attributes:
 
255
        options:        Command line options
 
256
        clients:        Set() of Client objects
 
257
        credentials:    GnuTLS X.509 credentials
 
258
    """
 
259
    address_family = socket.AF_INET6
 
260
    def __init__(self, *args, **kwargs):
 
261
        if "options" in kwargs:
 
262
            self.options = kwargs["options"]
 
263
            del kwargs["options"]
 
264
        if "clients" in kwargs:
 
265
            self.clients = kwargs["clients"]
 
266
            del kwargs["clients"]
 
267
        if "credentials" in kwargs:
 
268
            self.credentials = kwargs["credentials"]
 
269
            del kwargs["credentials"]
 
270
        return super(type(self), self).__init__(*args, **kwargs)
 
271
    def server_bind(self):
 
272
        """This overrides the normal server_bind() function
 
273
        to bind to an interface if one was specified, and also NOT to
 
274
        bind to an address or port if they were not specified."""
 
275
        if self.options.interface:
 
276
            if not hasattr(socket, "SO_BINDTODEVICE"):
 
277
                # From /usr/include/asm-i486/socket.h
 
278
                socket.SO_BINDTODEVICE = 25
 
279
            try:
 
280
                self.socket.setsockopt(socket.SOL_SOCKET,
 
281
                                       socket.SO_BINDTODEVICE,
 
282
                                       self.options.interface)
 
283
            except socket.error, error:
 
284
                if error[0] == errno.EPERM:
 
285
                    sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
 
286
                                     % self.options.interface)
 
287
                else:
 
288
                    raise error
 
289
        # Only bind(2) the socket if we really need to.
 
290
        if self.server_address[0] or self.server_address[1]:
 
291
            if not self.server_address[0]:
 
292
                in6addr_any = "::"
 
293
                self.server_address = (in6addr_any,
 
294
                                       self.server_address[1])
 
295
            elif self.server_address[1] is None:
 
296
                self.server_address = (self.server_address[0],
 
297
                                       0)
 
298
            return super(type(self), self).server_bind()
 
299
 
127
300
 
128
301
def string_to_delta(interval):
129
302
    """Parse a string and return a datetime.timedelta
158
331
        raise ValueError
159
332
    return delta
160
333
 
161
 
def main():
 
334
 
 
335
def add_service():
 
336
    """From the Avahi server example code"""
 
337
    global group, serviceName, serviceType, servicePort, serviceTXT, \
 
338
           domain, host
 
339
    if group is None:
 
340
        group = dbus.Interface(
 
341
                bus.get_object( avahi.DBUS_NAME,
 
342
                                server.EntryGroupNew()),
 
343
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
344
        group.connect_to_signal('StateChanged',
 
345
                                entry_group_state_changed)
 
346
    
 
347
    # print "Adding service '%s' of type '%s' ..." % (serviceName,
 
348
    #                                                 serviceType)
 
349
    
 
350
    group.AddService(
 
351
            serviceInterface,           # interface
 
352
            avahi.PROTO_INET6,          # protocol
 
353
            dbus.UInt32(0),             # flags
 
354
            serviceName, serviceType,
 
355
            domain, host,
 
356
            dbus.UInt16(servicePort),
 
357
            avahi.string_array_to_txt_array(serviceTXT))
 
358
    group.Commit()
 
359
 
 
360
 
 
361
def remove_service():
 
362
    """From the Avahi server example code"""
 
363
    global group
 
364
    
 
365
    if not group is None:
 
366
        group.Reset()
 
367
 
 
368
 
 
369
def server_state_changed(state):
 
370
    """From the Avahi server example code"""
 
371
    if state == avahi.SERVER_COLLISION:
 
372
        print "WARNING: Server name collision"
 
373
        remove_service()
 
374
    elif state == avahi.SERVER_RUNNING:
 
375
        add_service()
 
376
 
 
377
 
 
378
def entry_group_state_changed(state, error):
 
379
    """From the Avahi server example code"""
 
380
    global serviceName, server, rename_count
 
381
    
 
382
    # print "state change: %i" % state
 
383
    
 
384
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
385
        pass
 
386
        # print "Service established."
 
387
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
388
        
 
389
        rename_count = rename_count - 1
 
390
        if rename_count > 0:
 
391
            name = server.GetAlternativeServiceName(name)
 
392
            print "WARNING: Service name collision, changing name to '%s' ..." % name
 
393
            remove_service()
 
394
            add_service()
 
395
            
 
396
        else:
 
397
            print "ERROR: No suitable service name found after %i retries, exiting." % n_rename
 
398
            main_loop.quit()
 
399
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
400
        print "Error in group state changed", error
 
401
        main_loop.quit()
 
402
        return
 
403
 
 
404
 
 
405
def if_nametoindex(interface):
 
406
    """Call the C function if_nametoindex()"""
 
407
    try:
 
408
        if "ctypes" not in sys.modules:
 
409
            import ctypes
 
410
        libc = ctypes.cdll.LoadLibrary("libc.so.6")
 
411
        return libc.if_nametoindex(interface)
 
412
    except (ImportError, OSError, AttributeError):
 
413
        if "struct" not in sys.modules:
 
414
            import struct
 
415
        if "fcntl" not in sys.modules:
 
416
            import fcntl
 
417
        SIOCGIFINDEX = 0x8933      # From /usr/include/linux/sockios.h
 
418
        s = socket.socket()
 
419
        ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
420
                            struct.pack("16s16x", interface))
 
421
        s.close()
 
422
        interface_index = struct.unpack("I", ifreq[16:20])[0]
 
423
        return interface_index
 
424
 
 
425
 
 
426
if __name__ == '__main__':
162
427
    parser = OptionParser()
163
428
    parser.add_option("-i", "--interface", type="string",
164
 
                      default="eth0", metavar="IF",
165
 
                      help="Interface to bind to")
 
429
                      default=None, metavar="IF",
 
430
                      help="Bind to interface IF")
166
431
    parser.add_option("--cert", type="string", default="cert.pem",
167
432
                      metavar="FILE",
168
 
                      help="Public key certificate to use")
 
433
                      help="Public key certificate PEM file to use")
169
434
    parser.add_option("--key", type="string", default="key.pem",
170
435
                      metavar="FILE",
171
 
                      help="Private key to use")
 
436
                      help="Private key PEM file to use")
172
437
    parser.add_option("--ca", type="string", default="ca.pem",
173
438
                      metavar="FILE",
174
 
                      help="Certificate Authority certificate to use")
 
439
                      help="Certificate Authority certificate PEM file to use")
175
440
    parser.add_option("--crl", type="string", default="crl.pem",
176
441
                      metavar="FILE",
177
 
                      help="Certificate Revokation List to use")
178
 
    parser.add_option("-p", "--port", type="int", default=49001,
 
442
                      help="Certificate Revokation List PEM file to use")
 
443
    parser.add_option("-p", "--port", type="int", default=None,
179
444
                      help="Port number to receive requests on")
180
 
    parser.add_option("--dh", type="int", metavar="BITS",
181
 
                      help="DH group to use")
182
 
    parser.add_option("-t", "--timeout", type="string", # Parsed later
183
 
                      default="15m",
 
445
    parser.add_option("--timeout", type="string", # Parsed later
 
446
                      default="1h",
184
447
                      help="Amount of downtime allowed for clients")
185
448
    parser.add_option("--interval", type="string", # Parsed later
186
449
                      default="5m",
188
451
    parser.add_option("--check", action="store_true", default=False,
189
452
                      help="Run self-test")
190
453
    (options, args) = parser.parse_args()
191
 
 
 
454
    
192
455
    if options.check:
193
456
        import doctest
194
457
        doctest.testmod()
199
462
        options.timeout = string_to_delta(options.timeout)
200
463
    except ValueError:
201
464
        parser.error("option --timeout: Unparseable time")
202
 
    
203
465
    try:
204
466
        options.interval = string_to_delta(options.interval)
205
467
    except ValueError:
213
475
    
214
476
    # Parse config file
215
477
    defaults = {}
216
 
    client_config_object = ConfigParser.SafeConfigParser(defaults)
217
 
    client_config_object.read("mandos-clients.conf")
218
 
    clients = [Client(name=section, options=options,
219
 
                      **(dict(client_config_object.items(section))))
220
 
               for section in client_config_object.sections()]
221
 
    
222
 
    udp_server = IPv6_UDPServer((in6addr_any, options.port),
223
 
                                udp_handler,
224
 
                                options=options)
225
 
    
226
 
    tcp_server = IPv6_TCPServer((in6addr_any, options.port),
 
478
    client_config = ConfigParser.SafeConfigParser(defaults)
 
479
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
 
480
    client_config.read("mandos-clients.conf")
 
481
    
 
482
    # From the Avahi server example code
 
483
    DBusGMainLoop(set_as_default=True )
 
484
    main_loop = gobject.MainLoop()
 
485
    bus = dbus.SystemBus()
 
486
    server = dbus.Interface(
 
487
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
 
488
            avahi.DBUS_INTERFACE_SERVER )
 
489
    # End of Avahi example code
 
490
    
 
491
    clients = Set()
 
492
    def remove_from_clients(client):
 
493
        clients.remove(client)
 
494
        if not clients:
 
495
            print "No clients left, exiting"
 
496
            main_loop.quit()
 
497
    
 
498
    clients.update(Set(Client(name=section, options=options,
 
499
                              stop_hook = remove_from_clients,
 
500
                              **(dict(client_config\
 
501
                                      .items(section))))
 
502
                       for section in client_config.sections()))
 
503
    for client in clients:
 
504
        client.start()
 
505
    
 
506
    tcp_server = IPv6_TCPServer((None, options.port),
227
507
                                tcp_handler,
228
508
                                options=options,
229
509
                                clients=clients,
230
510
                                credentials=cred)
231
 
    
232
 
    while True:
233
 
        in_, out, err = select.select((udp_server,
234
 
                                       tcp_server), (), ())
235
 
        for server in in_:
236
 
            server.handle_request()
237
 
 
238
 
 
239
 
if __name__ == "__main__":
240
 
    main()
241
 
 
 
511
    # Find out what random port we got
 
512
    servicePort = tcp_server.socket.getsockname()[1]
 
513
    #sys.stderr.write("Now listening on port %d\n" % servicePort)
 
514
    
 
515
    if options.interface is not None:
 
516
        serviceInterface = if_nametoindex(options.interface)
 
517
    
 
518
    # From the Avahi server example code
 
519
    server.connect_to_signal("StateChanged", server_state_changed)
 
520
    server_state_changed(server.GetState())
 
521
    # End of Avahi example code
 
522
    
 
523
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
 
524
                         lambda *args, **kwargs:
 
525
                         tcp_server.handle_request(*args[2:],
 
526
                                                   **kwargs) or True)
 
527
    try:
 
528
        main_loop.run()
 
529
    except KeyboardInterrupt:
 
530
        print
 
531
    
 
532
    # Cleanup here
 
533
 
 
534
    # From the Avahi server example code
 
535
    if not group is None:
 
536
        group.Free()
 
537
    # End of Avahi example code
 
538
    
 
539
    for client in clients:
 
540
        client.stop_hook = None
 
541
        client.stop()