/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-01-18 22:04:19 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080118220419-z95418owrporioo5
* Makefile (client_debug): New.
* client.cpp (CERT_ROOT): New.

(Tested)

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
 
 
5
3
import SocketServer
6
4
import socket
7
5
import select
13
11
import gnutls.errors
14
12
import ConfigParser
15
13
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
42
14
 
43
15
 
44
16
class Client(object):
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,
 
17
    def __init__(self, name=None, options=None, dn=None,
 
18
                 password=None, passfile=None, fqdn=None,
66
19
                 timeout=None, interval=-1):
67
20
        self.name = name
68
21
        self.dn = dn
71
24
        elif passfile:
72
25
            self.password = open(passfile).readall()
73
26
        else:
74
 
            raise RuntimeError(u"No Password or Passfile for client %s"
75
 
                               % self.name)
76
 
        self.fqdn = fqdn                # string
 
27
            print "No Password or Passfile in client config file"
 
28
            # raise RuntimeError XXX
 
29
            self.password = "gazonk"
 
30
        self.fqdn = fqdn
77
31
        self.created = datetime.datetime.now()
78
32
        self.last_seen = None
79
33
        if timeout is None:
80
34
            timeout = options.timeout
81
35
        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))
87
36
        if interval == -1:
88
37
            interval = options.interval
89
 
        else:
90
 
            interval = string_to_delta(interval)
91
38
        self.interval = interval
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)
 
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"
201
90
 
202
91
 
203
92
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."""
207
93
    def handle(self):
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
 
94
        print "TCP request came"
 
95
        print "Request:", self.request
 
96
        print "Client Address:", self.client_address
 
97
        print "Server:", self.server
212
98
        session = gnutls.connection.ServerSession(self.request,
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
 
99
                                                  self.server.credentials)
 
100
        session.handshake()
 
101
        if session.peer_certificate:
 
102
            print "DN:", session.peer_certificate.subject
224
103
        try:
225
104
            session.verify_peer()
226
105
        except gnutls.errors.CertificateError, error:
227
 
            #sys.stderr.write(u"Verify failed: %s\n" % error)
 
106
            print "Verify failed", error
228
107
            session.bye()
229
108
            return
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
 
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
249
116
        session.bye()
250
117
 
251
118
 
252
119
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
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
 
 
 
120
    __metaclass__ = server_metaclass
 
121
    request_queue_size = 1024
 
122
 
 
123
 
 
124
in6addr_any = "::"
 
125
 
 
126
cred = None
300
127
 
301
128
def string_to_delta(interval):
302
129
    """Parse a string and return a datetime.timedelta
331
158
        raise ValueError
332
159
    return delta
333
160
 
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__':
 
161
def main():
427
162
    parser = OptionParser()
428
163
    parser.add_option("-i", "--interface", type="string",
429
 
                      default=None, metavar="IF",
430
 
                      help="Bind to interface IF")
 
164
                      default="eth0", metavar="IF",
 
165
                      help="Interface to bind to")
431
166
    parser.add_option("--cert", type="string", default="cert.pem",
432
167
                      metavar="FILE",
433
 
                      help="Public key certificate PEM file to use")
 
168
                      help="Public key certificate to use")
434
169
    parser.add_option("--key", type="string", default="key.pem",
435
170
                      metavar="FILE",
436
 
                      help="Private key PEM file to use")
 
171
                      help="Private key to use")
437
172
    parser.add_option("--ca", type="string", default="ca.pem",
438
173
                      metavar="FILE",
439
 
                      help="Certificate Authority certificate PEM file to use")
 
174
                      help="Certificate Authority certificate to use")
440
175
    parser.add_option("--crl", type="string", default="crl.pem",
441
176
                      metavar="FILE",
442
 
                      help="Certificate Revokation List PEM file to use")
443
 
    parser.add_option("-p", "--port", type="int", default=None,
 
177
                      help="Certificate Revokation List to use")
 
178
    parser.add_option("-p", "--port", type="int", default=49001,
444
179
                      help="Port number to receive requests on")
445
 
    parser.add_option("--timeout", type="string", # Parsed later
446
 
                      default="1h",
 
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",
447
184
                      help="Amount of downtime allowed for clients")
448
185
    parser.add_option("--interval", type="string", # Parsed later
449
186
                      default="5m",
451
188
    parser.add_option("--check", action="store_true", default=False,
452
189
                      help="Run self-test")
453
190
    (options, args) = parser.parse_args()
454
 
    
 
191
 
455
192
    if options.check:
456
193
        import doctest
457
194
        doctest.testmod()
462
199
        options.timeout = string_to_delta(options.timeout)
463
200
    except ValueError:
464
201
        parser.error("option --timeout: Unparseable time")
 
202
    
465
203
    try:
466
204
        options.interval = string_to_delta(options.interval)
467
205
    except ValueError:
475
213
    
476
214
    # Parse config file
477
215
    defaults = {}
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),
 
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),
507
227
                                tcp_handler,
508
228
                                options=options,
509
229
                                clients=clients,
510
230
                                credentials=cred)
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()
 
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