/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: Björn Påhlsson
  • Date: 2008-07-21 03:58:36 UTC
  • mfrom: (17 mandos)
  • mto: This revision was merged to the branch mainline in revision 20.
  • Revision ID: belorn@braxen-20080721035836-v7220bmolr608r61
Added getopt_long support for mandosclient and passprompt
Added support for --interface for mandosclient
Added support for --prefix for passprompt

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
 
2
# -*- mode: python; coding: utf-8 -*-
 
3
 
4
# Mandos server - give out binary blobs to connecting clients.
 
5
 
6
# This program is partly derived from an example program for an Avahi
 
7
# service publisher, downloaded from
 
8
# <http://avahi.org/wiki/PythonPublishExample>.  This includes the
 
9
# following functions: "add_service", "remove_service",
 
10
# "server_state_changed", "entry_group_state_changed", and some lines
 
11
# in "main".
 
12
 
13
# Everything else is Copyright © 2007-2008 Teddy Hogeborn and Björn
 
14
# Påhlsson.
 
15
 
16
# This program is free software: you can redistribute it and/or modify
 
17
# it under the terms of the GNU General Public License as published by
 
18
# the Free Software Foundation, either version 3 of the License, or
 
19
# (at your option) any later version.
 
20
#
 
21
#     This program is distributed in the hope that it will be useful,
 
22
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
 
23
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
24
#     GNU General Public License for more details.
 
25
 
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/>.
 
28
 
29
# Contact the authors at <https://www.fukt.bsnet.se/~belorn/> and
 
30
# <https://www.fukt.bsnet.se/~teddy/>.
 
31
2
32
 
3
33
from __future__ import division
4
34
 
21
51
import signal
22
52
from sets import Set
23
53
import subprocess
 
54
import atexit
 
55
import stat
 
56
import logging
 
57
import logging.handlers
24
58
 
25
59
import dbus
26
60
import gobject
28
62
from dbus.mainloop.glib import DBusGMainLoop
29
63
import ctypes
30
64
 
 
65
# Brief description of the operation of this program:
 
66
 
67
# This server announces itself as a Zeroconf service.  Connecting
 
68
# clients use the TLS protocol, with the unusual quirk that this
 
69
# server program acts as a TLS "client" while the connecting clients
 
70
# acts as a TLS "server".  The clients (acting as a TLS "server") must
 
71
# supply an OpenPGP certificate, and the fingerprint of this
 
72
# certificate is used by this server to look up (in a list read from a
 
73
# file at start time) which binary blob to give the client.  No other
 
74
# authentication or authorization is done by this server.
 
75
 
 
76
 
 
77
logger = logging.Logger('mandos')
 
78
syslogger = logging.handlers.SysLogHandler\
 
79
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON)
 
80
syslogger.setFormatter(logging.Formatter\
 
81
                        ('%(levelname)s: %(message)s'))
 
82
logger.addHandler(syslogger)
 
83
del syslogger
 
84
 
31
85
# This variable is used to optionally bind to a specified interface.
32
86
# It is a global variable to fit in with the other variables from the
33
 
# Avahi server example code.
 
87
# Avahi example code.
34
88
serviceInterface = avahi.IF_UNSPEC
35
 
# From the Avahi server example code:
 
89
# From the Avahi example code:
36
90
serviceName = "Mandos"
37
91
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
38
92
servicePort = None                      # Not known at startup
99
153
                        _set_interval)
100
154
    del _set_interval
101
155
    def __init__(self, name=None, options=None, stop_hook=None,
102
 
                 fingerprint=None, secret=None, secfile=None, fqdn=None,
103
 
                 timeout=None, interval=-1, checker=None):
 
156
                 fingerprint=None, secret=None, secfile=None,
 
157
                 fqdn=None, timeout=None, interval=-1, checker=None):
 
158
        """Note: the 'checker' argument sets the 'checker_command'
 
159
        attribute and not the 'checker' attribute.."""
104
160
        self.name = name
105
161
        # Uppercase and remove spaces from fingerprint
106
162
        # for later comparison purposes with return value of
119
175
        self.created = datetime.datetime.now()
120
176
        self.last_seen = None
121
177
        if timeout is None:
122
 
            timeout = options.timeout
123
 
        self.timeout = timeout
 
178
            self.timeout = options.timeout
 
179
        else:
 
180
            self.timeout = string_to_delta(timeout)
124
181
        if interval == -1:
125
 
            interval = options.interval
 
182
            self.interval = options.interval
126
183
        else:
127
 
            interval = string_to_delta(interval)
128
 
        self.interval = interval
 
184
            self.interval = string_to_delta(interval)
129
185
        self.stop_hook = stop_hook
130
186
        self.checker = None
131
187
        self.checker_initiator_tag = None
133
189
        self.checker_callback_tag = None
134
190
        self.check_command = checker
135
191
    def start(self):
136
 
        """Start this clients checker and timeout hooks"""
 
192
        """Start this client's checker and timeout hooks"""
137
193
        # Schedule a new checker to be started an 'interval' from now,
138
194
        # and every interval from then on.
139
195
        self.checker_initiator_tag = gobject.timeout_add\
149
205
        """Stop this client.
150
206
        The possibility that this client might be restarted is left
151
207
        open, but not currently used."""
152
 
        if debug:
153
 
            sys.stderr.write(u"Stopping client %s\n" % self.name)
154
 
        self.secret = None
155
 
        if self.stop_initiator_tag:
 
208
        # If this client doesn't have a secret, it is already stopped.
 
209
        if self.secret:
 
210
            logger.debug(u"Stopping client %s", self.name)
 
211
            self.secret = None
 
212
        else:
 
213
            return False
 
214
        if hasattr(self, "stop_initiator_tag") \
 
215
               and self.stop_initiator_tag:
156
216
            gobject.source_remove(self.stop_initiator_tag)
157
217
            self.stop_initiator_tag = None
158
 
        if self.checker_initiator_tag:
 
218
        if hasattr(self, "checker_initiator_tag") \
 
219
               and self.checker_initiator_tag:
159
220
            gobject.source_remove(self.checker_initiator_tag)
160
221
            self.checker_initiator_tag = None
161
222
        self.stop_checker()
164
225
        # Do not run this again if called by a gobject.timeout_add
165
226
        return False
166
227
    def __del__(self):
167
 
        # Some code duplication here and in stop()
168
 
        if hasattr(self, "stop_initiator_tag") \
169
 
               and self.stop_initiator_tag:
170
 
            gobject.source_remove(self.stop_initiator_tag)
171
 
            self.stop_initiator_tag = None
172
 
        if hasattr(self, "checker_initiator_tag") \
173
 
               and self.checker_initiator_tag:
174
 
            gobject.source_remove(self.checker_initiator_tag)
175
 
            self.checker_initiator_tag = None
176
 
        self.stop_checker()
 
228
        self.stop_hook = None
 
229
        self.stop()
177
230
    def checker_callback(self, pid, condition):
178
231
        """The checker has completed, so take appropriate actions."""
179
232
        now = datetime.datetime.now()
 
233
        self.checker_callback_tag = None
 
234
        self.checker = None
180
235
        if os.WIFEXITED(condition) \
181
236
               and (os.WEXITSTATUS(condition) == 0):
182
 
            if debug:
183
 
                sys.stderr.write(u"Checker for %(name)s succeeded\n"
184
 
                                 % vars(self))
 
237
            logger.debug(u"Checker for %(name)s succeeded",
 
238
                         vars(self))
185
239
            self.last_seen = now
186
240
            gobject.source_remove(self.stop_initiator_tag)
187
241
            self.stop_initiator_tag = gobject.timeout_add\
188
242
                                      (self._timeout_milliseconds,
189
243
                                       self.stop)
190
 
        elif debug:
191
 
            if not os.WIFEXITED(condition):
192
 
                sys.stderr.write(u"Checker for %(name)s crashed?\n"
193
 
                                 % vars(self))
194
 
            else:
195
 
                sys.stderr.write(u"Checker for %(name)s failed\n"
196
 
                                 % vars(self))
197
 
        self.checker = None
198
 
        self.checker_callback_tag = None
 
244
        elif not os.WIFEXITED(condition):
 
245
            logger.warning(u"Checker for %(name)s crashed?",
 
246
                           vars(self))
 
247
        else:
 
248
            logger.debug(u"Checker for %(name)s failed",
 
249
                         vars(self))
199
250
    def start_checker(self):
200
251
        """Start a new checker subprocess if one is not running.
201
252
        If a checker already exists, leave it running and do
202
253
        nothing."""
 
254
        # The reason for not killing a running checker is that if we
 
255
        # did that, then if a checker (for some reason) started
 
256
        # running slowly and taking more than 'interval' time, the
 
257
        # client would inevitably timeout, since no checker would get
 
258
        # a chance to run to completion.  If we instead leave running
 
259
        # checkers alone, the checker would have to take more time
 
260
        # than 'timeout' for the client to be declared invalid, which
 
261
        # is as it should be.
203
262
        if self.checker is None:
204
 
            if debug:
205
 
                sys.stderr.write(u"Starting checker for %s\n"
206
 
                                 % self.name)
207
263
            try:
208
264
                command = self.check_command % self.fqdn
209
265
            except TypeError:
210
266
                escaped_attrs = dict((key, re.escape(str(val)))
211
267
                                     for key, val in
212
268
                                     vars(self).iteritems())
213
 
                command = self.check_command % escaped_attrs
 
269
                try:
 
270
                    command = self.check_command % escaped_attrs
 
271
                except TypeError, error:
 
272
                    logger.critical(u'Could not format string "%s":'
 
273
                                    u' %s', self.check_command, error)
 
274
                    return True # Try again later
214
275
            try:
 
276
                logger.debug(u"Starting checker %r for %s",
 
277
                             command, self.name)
215
278
                self.checker = subprocess.\
216
279
                               Popen(command,
217
 
                                     stdout=subprocess.PIPE,
218
280
                                     close_fds=True, shell=True,
219
281
                                     cwd="/")
220
 
                self.checker_callback_tag = gobject.\
221
 
                                            child_watch_add(self.checker.pid,
222
 
                                                            self.\
223
 
                                                            checker_callback)
 
282
                self.checker_callback_tag = gobject.child_watch_add\
 
283
                                            (self.checker.pid,
 
284
                                             self.checker_callback)
224
285
            except subprocess.OSError, error:
225
 
                sys.stderr.write(u"Failed to start subprocess: %s\n"
226
 
                                 % error)
 
286
                logger.error(u"Failed to start subprocess: %s",
 
287
                             error)
227
288
        # Re-run this periodically if run by gobject.timeout_add
228
289
        return True
229
290
    def stop_checker(self):
230
291
        """Force the checker process, if any, to stop."""
 
292
        if self.checker_callback_tag:
 
293
            gobject.source_remove(self.checker_callback_tag)
 
294
            self.checker_callback_tag = None
231
295
        if not hasattr(self, "checker") or self.checker is None:
232
296
            return
233
 
        gobject.source_remove(self.checker_callback_tag)
234
 
        self.checker_callback_tag = None
235
 
        os.kill(self.checker.pid, signal.SIGTERM)
236
 
        if self.checker.poll() is None:
237
 
            os.kill(self.checker.pid, signal.SIGKILL)
 
297
        logger.debug("Stopping checker for %(name)s", vars(self))
 
298
        try:
 
299
            os.kill(self.checker.pid, signal.SIGTERM)
 
300
            #os.sleep(0.5)
 
301
            #if self.checker.poll() is None:
 
302
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
303
        except OSError, error:
 
304
            if error.errno != errno.ESRCH:
 
305
                raise
238
306
        self.checker = None
239
307
    def still_valid(self, now=None):
240
308
        """Has the timeout not yet passed for this client?"""
247
315
 
248
316
 
249
317
def peer_certificate(session):
 
318
    "Return the peer's OpenPGP certificate as a bytestring"
250
319
    # If not an OpenPGP certificate...
251
320
    if gnutls.library.functions.gnutls_certificate_type_get\
252
321
            (session._c_object) \
263
332
 
264
333
 
265
334
def fingerprint(openpgp):
 
335
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
266
336
    # New empty GnuTLS certificate
267
337
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
268
338
    gnutls.library.functions.gnutls_openpgp_crt_init\
298
368
    Note: This will run in its own forked process."""
299
369
    
300
370
    def handle(self):
301
 
        if debug:
302
 
            sys.stderr.write(u"TCP request came\n")
303
 
            sys.stderr.write(u"Request: %s\n" % self.request)
304
 
            sys.stderr.write(u"Client Address: %s\n"
305
 
                             % unicode(self.client_address))
306
 
            sys.stderr.write(u"Server: %s\n" % self.server)
 
371
        logger.debug(u"TCP connection from: %s",
 
372
                     unicode(self.client_address))
307
373
        session = gnutls.connection.ClientSession(self.request,
308
374
                                                  gnutls.connection.\
309
375
                                                  X509Credentials())
319
385
        try:
320
386
            session.handshake()
321
387
        except gnutls.errors.GNUTLSError, error:
322
 
            if debug:
323
 
                sys.stderr.write(u"Handshake failed: %s\n" % error)
 
388
            logger.debug(u"Handshake failed: %s", error)
324
389
            # Do not run session.bye() here: the session is not
325
390
            # established.  Just abandon the request.
326
391
            return
327
392
        try:
328
393
            fpr = fingerprint(peer_certificate(session))
329
394
        except (TypeError, gnutls.errors.GNUTLSError), error:
330
 
            if debug:
331
 
                sys.stderr.write(u"Bad certificate: %s\n" % error)
 
395
            logger.debug(u"Bad certificate: %s", error)
332
396
            session.bye()
333
397
            return
334
 
        if debug:
335
 
            sys.stderr.write(u"Fingerprint: %s\n" % fpr)
 
398
        logger.debug(u"Fingerprint: %s", fpr)
336
399
        client = None
337
 
        for c in clients:
 
400
        for c in self.server.clients:
338
401
            if c.fingerprint == fpr:
339
402
                client = c
340
403
                break
342
405
        # that the client timed out while establishing the GnuTLS
343
406
        # session.
344
407
        if (not client) or (not client.still_valid()):
345
 
            if debug:
346
 
                if client:
347
 
                    sys.stderr.write(u"Client %(name)s is invalid\n"
348
 
                                     % vars(client))
349
 
                else:
350
 
                    sys.stderr.write(u"Client not found for "
351
 
                                     u"fingerprint: %s\n" % fpr)
 
408
            if client:
 
409
                logger.debug(u"Client %(name)s is invalid",
 
410
                             vars(client))
 
411
            else:
 
412
                logger.debug(u"Client not found for fingerprint: %s",
 
413
                             fpr)
352
414
            session.bye()
353
415
            return
354
416
        sent_size = 0
355
417
        while sent_size < len(client.secret):
356
418
            sent = session.send(client.secret[sent_size:])
357
 
            if debug:
358
 
                sys.stderr.write(u"Sent: %d, remaining: %d\n"
359
 
                                 % (sent, len(client.secret)
360
 
                                    - (sent_size + sent)))
 
419
            logger.debug(u"Sent: %d, remaining: %d",
 
420
                         sent, len(client.secret)
 
421
                         - (sent_size + sent))
361
422
            sent_size += sent
362
423
        session.bye()
363
424
 
391
452
                                       self.options.interface)
392
453
            except socket.error, error:
393
454
                if error[0] == errno.EPERM:
394
 
                    sys.stderr.write(u"Warning: No permission to" \
395
 
                                     u" bind to interface %s\n"
396
 
                                     % self.options.interface)
 
455
                    logger.warning(u"No permission to"
 
456
                                   u" bind to interface %s",
 
457
                                   self.options.interface)
397
458
                else:
398
459
                    raise error
399
460
        # Only bind(2) the socket if we really need to.
443
504
 
444
505
 
445
506
def add_service():
446
 
    """From the Avahi server example code"""
 
507
    """Derived from the Avahi example code"""
447
508
    global group, serviceName, serviceType, servicePort, serviceTXT, \
448
509
           domain, host
449
510
    if group is None:
453
514
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
454
515
        group.connect_to_signal('StateChanged',
455
516
                                entry_group_state_changed)
456
 
    if debug:
457
 
        sys.stderr.write(u"Adding service '%s' of type '%s' ...\n"
458
 
                         % (serviceName, serviceType))
 
517
    logger.debug(u"Adding service '%s' of type '%s' ...",
 
518
                 serviceName, serviceType)
459
519
    
460
520
    group.AddService(
461
521
            serviceInterface,           # interface
469
529
 
470
530
 
471
531
def remove_service():
472
 
    """From the Avahi server example code"""
 
532
    """From the Avahi example code"""
473
533
    global group
474
534
    
475
535
    if not group is None:
477
537
 
478
538
 
479
539
def server_state_changed(state):
480
 
    """From the Avahi server example code"""
 
540
    """Derived from the Avahi example code"""
481
541
    if state == avahi.SERVER_COLLISION:
482
 
        sys.stderr.write(u"WARNING: Server name collision\n")
 
542
        logger.warning(u"Server name collision")
483
543
        remove_service()
484
544
    elif state == avahi.SERVER_RUNNING:
485
545
        add_service()
486
546
 
487
547
 
488
548
def entry_group_state_changed(state, error):
489
 
    """From the Avahi server example code"""
 
549
    """Derived from the Avahi example code"""
490
550
    global serviceName, server, rename_count
491
551
    
492
 
    if debug:
493
 
        sys.stderr.write(u"state change: %i\n" % state)
 
552
    logger.debug(u"state change: %i", state)
494
553
    
495
554
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
496
 
        if debug:
497
 
            sys.stderr.write(u"Service established.\n")
 
555
        logger.debug(u"Service established.")
498
556
    elif state == avahi.ENTRY_GROUP_COLLISION:
499
557
        
500
558
        rename_count = rename_count - 1
501
559
        if rename_count > 0:
502
560
            name = server.GetAlternativeServiceName(name)
503
 
            sys.stderr.write(u"WARNING: Service name collision, "
504
 
                             u"changing name to '%s' ...\n" % name)
 
561
            logger.warning(u"Service name collision, "
 
562
                           u"changing name to '%s' ...", name)
505
563
            remove_service()
506
564
            add_service()
507
565
            
508
566
        else:
509
 
            sys.stderr.write(u"ERROR: No suitable service name found "
510
 
                             u"after %i retries, exiting.\n"
511
 
                             % n_rename)
512
 
            main_loop.quit()
 
567
            logger.error(u"No suitable service name found after %i"
 
568
                         u" retries, exiting.", n_rename)
 
569
            killme(1)
513
570
    elif state == avahi.ENTRY_GROUP_FAILURE:
514
 
        sys.stderr.write(u"Error in group state changed %s\n"
515
 
                         % unicode(error))
516
 
        main_loop.quit()
517
 
        return
 
571
        logger.error(u"Error in group state changed %s",
 
572
                     unicode(error))
 
573
        killme(1)
518
574
 
519
575
 
520
576
def if_nametoindex(interface):
536
592
        return interface_index
537
593
 
538
594
 
539
 
if __name__ == '__main__':
 
595
def daemon(nochdir, noclose):
 
596
    """See daemon(3).  Standard BSD Unix function.
 
597
    This should really exist as os.daemon, but it doesn't (yet)."""
 
598
    if os.fork():
 
599
        sys.exit()
 
600
    os.setsid()
 
601
    if not nochdir:
 
602
        os.chdir("/")
 
603
    if not noclose:
 
604
        # Close all standard open file descriptors
 
605
        null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
 
606
        if not stat.S_ISCHR(os.fstat(null).st_mode):
 
607
            raise OSError(errno.ENODEV,
 
608
                          "/dev/null not a character device")
 
609
        os.dup2(null, sys.stdin.fileno())
 
610
        os.dup2(null, sys.stdout.fileno())
 
611
        os.dup2(null, sys.stderr.fileno())
 
612
        if null > 2:
 
613
            os.close(null)
 
614
 
 
615
 
 
616
def killme(status = 0):
 
617
    logger.debug("Stopping server with exit status %d", status)
 
618
    exitstatus = status
 
619
    if main_loop_started:
 
620
        main_loop.quit()
 
621
    else:
 
622
        sys.exit(status)
 
623
 
 
624
 
 
625
def main():
 
626
    global exitstatus
 
627
    exitstatus = 0
 
628
    global main_loop_started
 
629
    main_loop_started = False
 
630
    
540
631
    parser = OptionParser()
541
632
    parser.add_option("-i", "--interface", type="string",
542
633
                      default=None, metavar="IF",
543
634
                      help="Bind to interface IF")
544
 
    parser.add_option("--cert", type="string", default="cert.pem",
545
 
                      metavar="FILE",
546
 
                      help="Public key certificate PEM file to use")
547
 
    parser.add_option("--key", type="string", default="key.pem",
548
 
                      metavar="FILE",
549
 
                      help="Private key PEM file to use")
550
 
    parser.add_option("--ca", type="string", default="ca.pem",
551
 
                      metavar="FILE",
552
 
                      help="Certificate Authority certificate PEM file to use")
553
 
    parser.add_option("--crl", type="string", default="crl.pem",
554
 
                      metavar="FILE",
555
 
                      help="Certificate Revokation List PEM file to use")
 
635
    parser.add_option("-a", "--address", type="string", default=None,
 
636
                      help="Address to listen for requests on")
556
637
    parser.add_option("-p", "--port", type="int", default=None,
557
638
                      help="Port number to receive requests on")
558
639
    parser.add_option("--timeout", type="string", # Parsed later
583
664
        parser.error("option --interval: Unparseable time")
584
665
    
585
666
    # Parse config file
586
 
    defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
 
667
    defaults = { "checker": "fping -q -- %%(fqdn)s" }
587
668
    client_config = ConfigParser.SafeConfigParser(defaults)
588
 
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
 
669
    #client_config.readfp(open("global.conf"), "global.conf")
589
670
    client_config.read("mandos-clients.conf")
590
671
    
591
 
    # From the Avahi server example code
 
672
    global main_loop
 
673
    global bus
 
674
    global server
 
675
    # From the Avahi example code
592
676
    DBusGMainLoop(set_as_default=True )
593
677
    main_loop = gobject.MainLoop()
594
678
    bus = dbus.SystemBus()
599
683
    
600
684
    debug = options.debug
601
685
    
 
686
    if debug:
 
687
        console = logging.StreamHandler()
 
688
        # console.setLevel(logging.DEBUG)
 
689
        console.setFormatter(logging.Formatter\
 
690
                             ('%(levelname)s: %(message)s'))
 
691
        logger.addHandler(console)
 
692
        del console
 
693
    
602
694
    clients = Set()
603
695
    def remove_from_clients(client):
604
696
        clients.remove(client)
605
697
        if not clients:
606
 
            if debug:
607
 
                sys.stderr.write(u"No clients left, exiting\n")
608
 
            main_loop.quit()
 
698
            logger.debug(u"No clients left, exiting")
 
699
            killme()
609
700
    
610
701
    clients.update(Set(Client(name=section, options=options,
611
702
                              stop_hook = remove_from_clients,
612
703
                              **(dict(client_config\
613
704
                                      .items(section))))
614
705
                       for section in client_config.sections()))
 
706
    
 
707
    if not debug:
 
708
        daemon(False, False)
 
709
    
 
710
    def cleanup():
 
711
        "Cleanup function; run on exit"
 
712
        global group
 
713
        # From the Avahi example code
 
714
        if not group is None:
 
715
            group.Free()
 
716
            group = None
 
717
        # End of Avahi example code
 
718
        
 
719
        while clients:
 
720
            client = clients.pop()
 
721
            client.stop_hook = None
 
722
            client.stop()
 
723
    
 
724
    atexit.register(cleanup)
 
725
    
 
726
    if not debug:
 
727
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
728
    signal.signal(signal.SIGHUP, lambda signum, frame: killme())
 
729
    signal.signal(signal.SIGTERM, lambda signum, frame: killme())
 
730
    
615
731
    for client in clients:
616
732
        client.start()
617
733
    
618
 
    tcp_server = IPv6_TCPServer((None, options.port),
 
734
    tcp_server = IPv6_TCPServer((options.address, options.port),
619
735
                                tcp_handler,
620
736
                                options=options,
621
737
                                clients=clients)
622
738
    # Find out what random port we got
 
739
    global servicePort
623
740
    servicePort = tcp_server.socket.getsockname()[1]
624
 
    if debug:
625
 
        sys.stderr.write(u"Now listening on port %d\n" % servicePort)
 
741
    logger.debug(u"Now listening on port %d", servicePort)
626
742
    
627
743
    if options.interface is not None:
 
744
        global serviceInterface
628
745
        serviceInterface = if_nametoindex(options.interface)
629
746
    
630
 
    # From the Avahi server example code
 
747
    # From the Avahi example code
631
748
    server.connect_to_signal("StateChanged", server_state_changed)
632
 
    server_state_changed(server.GetState())
 
749
    try:
 
750
        server_state_changed(server.GetState())
 
751
    except dbus.exceptions.DBusException, error:
 
752
        logger.critical(u"DBusException: %s", error)
 
753
        killme(1)
633
754
    # End of Avahi example code
634
755
    
635
756
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
637
758
                         tcp_server.handle_request(*args[2:],
638
759
                                                   **kwargs) or True)
639
760
    try:
 
761
        logger.debug("Starting main loop")
 
762
        main_loop_started = True
640
763
        main_loop.run()
641
764
    except KeyboardInterrupt:
642
 
        print
 
765
        if debug:
 
766
            print
643
767
    
644
 
    # Cleanup here
 
768
    sys.exit(exitstatus)
645
769
 
646
 
    # From the Avahi server example code
647
 
    if not group is None:
648
 
        group.Free()
649
 
    # End of Avahi example code
650
 
    
651
 
    for client in clients:
652
 
        client.stop_hook = None
653
 
        client.stop()
 
770
if __name__ == '__main__':
 
771
    main()