/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-07-19 18:43:24 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080719184324-iwhoa5in75xa0u2u
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
                       [braxen_client]/password): Removed.
  ([foo]/fingerprint, [braxen_client]/fingerprint): New.
  ([foo]/checker): New.
  ([foo]/secfile): New.
  ([braxen_client]/secret): New.

* server.py: New "--debug" option to set debug flag.  Removed "cert",
             "key", "ca", "crl", and "cred" variables.  Added default
             value for "checker" config file setting.  Do not pass
             credentials to IPv6_TCPServer constructor.
  (debug): New global debug flag.  Used by most debugging output code.
  (Client.__init__): Keyword argument "dn" replaced by "fingerprint",
                     "password" renamed to "secret", and "passfile"
                     renamed to "secfile".  New keyword argument
                     "checker". All callers changed.
  (Client.dn): Removed.
  (Client.fingerprint): New.
  (Client.password): Renamed to "secret"; all users changed.
  (Client.passfile): Renamed to "secfile"; all users changed.
  (Client.timeout, Client.interval): Changed to be properties; now
                                     automatically updates the
                                     "_timeout_milliseconds" and
                                     "_interval_milliseconds" values.
  (Client.timeout_milliseconds): Renamed to "_timeout_milliseconds".
  (Client.interval_milliseconds): Renamed to "_interval_milliseconds".
  (Client.check_command): New.
  (Client.start_checker): Use the new "check_command" attribute.
  (peer_certificate, fingerprint): New functions.

  (tcp_handler.handle): Use ClientSession with empty credentials
                        object instead of ServerSession.  Set gnutls
                        priority string.  Do not verify peer.  Use
                        fingerprint instead of DN when searching for
                        clients.  Bug fix: Loop sending data so even large
                        secret data strings are sent.
  (IPv6_TCPServer.credentials): Removed.
  (if_nametoindex): Do not import ctypes since that is now imported
                    globally.

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
 
32
2
 
33
3
from __future__ import division
34
4
 
51
21
import signal
52
22
from sets import Set
53
23
import subprocess
54
 
import atexit
55
 
import stat
56
 
import logging
57
 
import logging.handlers
58
24
 
59
25
import dbus
60
26
import gobject
62
28
from dbus.mainloop.glib import DBusGMainLoop
63
29
import ctypes
64
30
 
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
 
 
85
31
# This variable is used to optionally bind to a specified interface.
86
32
# It is a global variable to fit in with the other variables from the
87
 
# Avahi example code.
 
33
# Avahi server example code.
88
34
serviceInterface = avahi.IF_UNSPEC
89
 
# From the Avahi example code:
 
35
# From the Avahi server example code:
90
36
serviceName = "Mandos"
91
37
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
38
servicePort = None                      # Not known at startup
153
99
                        _set_interval)
154
100
    del _set_interval
155
101
    def __init__(self, name=None, options=None, stop_hook=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.."""
 
102
                 fingerprint=None, secret=None, secfile=None, fqdn=None,
 
103
                 timeout=None, interval=-1, checker=None):
160
104
        self.name = name
161
105
        # Uppercase and remove spaces from fingerprint
162
106
        # for later comparison purposes with return value of
175
119
        self.created = datetime.datetime.now()
176
120
        self.last_seen = None
177
121
        if timeout is None:
178
 
            self.timeout = options.timeout
179
 
        else:
180
 
            self.timeout = string_to_delta(timeout)
 
122
            timeout = options.timeout
 
123
        self.timeout = timeout
181
124
        if interval == -1:
182
 
            self.interval = options.interval
 
125
            interval = options.interval
183
126
        else:
184
 
            self.interval = string_to_delta(interval)
 
127
            interval = string_to_delta(interval)
 
128
        self.interval = interval
185
129
        self.stop_hook = stop_hook
186
130
        self.checker = None
187
131
        self.checker_initiator_tag = None
189
133
        self.checker_callback_tag = None
190
134
        self.check_command = checker
191
135
    def start(self):
192
 
        """Start this client's checker and timeout hooks"""
 
136
        """Start this clients checker and timeout hooks"""
193
137
        # Schedule a new checker to be started an 'interval' from now,
194
138
        # and every interval from then on.
195
139
        self.checker_initiator_tag = gobject.timeout_add\
205
149
        """Stop this client.
206
150
        The possibility that this client might be restarted is left
207
151
        open, but not currently used."""
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:
 
152
        if debug:
 
153
            sys.stderr.write(u"Stopping client %s\n" % self.name)
 
154
        self.secret = None
 
155
        if self.stop_initiator_tag:
216
156
            gobject.source_remove(self.stop_initiator_tag)
217
157
            self.stop_initiator_tag = None
218
 
        if hasattr(self, "checker_initiator_tag") \
219
 
               and self.checker_initiator_tag:
 
158
        if self.checker_initiator_tag:
220
159
            gobject.source_remove(self.checker_initiator_tag)
221
160
            self.checker_initiator_tag = None
222
161
        self.stop_checker()
225
164
        # Do not run this again if called by a gobject.timeout_add
226
165
        return False
227
166
    def __del__(self):
228
 
        self.stop_hook = None
229
 
        self.stop()
 
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()
230
177
    def checker_callback(self, pid, condition):
231
178
        """The checker has completed, so take appropriate actions."""
232
179
        now = datetime.datetime.now()
233
 
        self.checker_callback_tag = None
234
 
        self.checker = None
235
180
        if os.WIFEXITED(condition) \
236
181
               and (os.WEXITSTATUS(condition) == 0):
237
 
            logger.debug(u"Checker for %(name)s succeeded",
238
 
                         vars(self))
 
182
            if debug:
 
183
                sys.stderr.write(u"Checker for %(name)s succeeded\n"
 
184
                                 % vars(self))
239
185
            self.last_seen = now
240
186
            gobject.source_remove(self.stop_initiator_tag)
241
187
            self.stop_initiator_tag = gobject.timeout_add\
242
188
                                      (self._timeout_milliseconds,
243
189
                                       self.stop)
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))
 
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
250
199
    def start_checker(self):
251
200
        """Start a new checker subprocess if one is not running.
252
201
        If a checker already exists, leave it running and do
253
202
        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.
262
203
        if self.checker is None:
 
204
            if debug:
 
205
                sys.stderr.write(u"Starting checker for %s\n"
 
206
                                 % self.name)
263
207
            try:
264
208
                command = self.check_command % self.fqdn
265
209
            except TypeError:
266
210
                escaped_attrs = dict((key, re.escape(str(val)))
267
211
                                     for key, val in
268
212
                                     vars(self).iteritems())
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
 
213
                command = self.check_command % escaped_attrs
275
214
            try:
276
 
                logger.debug(u"Starting checker %r for %s",
277
 
                             command, self.name)
278
215
                self.checker = subprocess.\
279
216
                               Popen(command,
 
217
                                     stdout=subprocess.PIPE,
280
218
                                     close_fds=True, shell=True,
281
219
                                     cwd="/")
282
 
                self.checker_callback_tag = gobject.child_watch_add\
283
 
                                            (self.checker.pid,
284
 
                                             self.checker_callback)
 
220
                self.checker_callback_tag = gobject.\
 
221
                                            child_watch_add(self.checker.pid,
 
222
                                                            self.\
 
223
                                                            checker_callback)
285
224
            except subprocess.OSError, error:
286
 
                logger.error(u"Failed to start subprocess: %s",
287
 
                             error)
 
225
                sys.stderr.write(u"Failed to start subprocess: %s\n"
 
226
                                 % error)
288
227
        # Re-run this periodically if run by gobject.timeout_add
289
228
        return True
290
229
    def stop_checker(self):
291
230
        """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
295
231
        if not hasattr(self, "checker") or self.checker is None:
296
232
            return
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
 
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)
306
238
        self.checker = None
307
239
    def still_valid(self, now=None):
308
240
        """Has the timeout not yet passed for this client?"""
315
247
 
316
248
 
317
249
def peer_certificate(session):
318
 
    "Return the peer's OpenPGP certificate as a bytestring"
319
250
    # If not an OpenPGP certificate...
320
251
    if gnutls.library.functions.gnutls_certificate_type_get\
321
252
            (session._c_object) \
332
263
 
333
264
 
334
265
def fingerprint(openpgp):
335
 
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
336
266
    # New empty GnuTLS certificate
337
267
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
338
268
    gnutls.library.functions.gnutls_openpgp_crt_init\
368
298
    Note: This will run in its own forked process."""
369
299
    
370
300
    def handle(self):
371
 
        logger.debug(u"TCP connection from: %s",
372
 
                     unicode(self.client_address))
 
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)
373
307
        session = gnutls.connection.ClientSession(self.request,
374
308
                                                  gnutls.connection.\
375
309
                                                  X509Credentials())
385
319
        try:
386
320
            session.handshake()
387
321
        except gnutls.errors.GNUTLSError, error:
388
 
            logger.debug(u"Handshake failed: %s", error)
 
322
            if debug:
 
323
                sys.stderr.write(u"Handshake failed: %s\n" % error)
389
324
            # Do not run session.bye() here: the session is not
390
325
            # established.  Just abandon the request.
391
326
            return
392
327
        try:
393
328
            fpr = fingerprint(peer_certificate(session))
394
329
        except (TypeError, gnutls.errors.GNUTLSError), error:
395
 
            logger.debug(u"Bad certificate: %s", error)
 
330
            if debug:
 
331
                sys.stderr.write(u"Bad certificate: %s\n" % error)
396
332
            session.bye()
397
333
            return
398
 
        logger.debug(u"Fingerprint: %s", fpr)
 
334
        if debug:
 
335
            sys.stderr.write(u"Fingerprint: %s\n" % fpr)
399
336
        client = None
400
 
        for c in self.server.clients:
 
337
        for c in clients:
401
338
            if c.fingerprint == fpr:
402
339
                client = c
403
340
                break
405
342
        # that the client timed out while establishing the GnuTLS
406
343
        # session.
407
344
        if (not client) or (not client.still_valid()):
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)
 
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)
414
352
            session.bye()
415
353
            return
416
354
        sent_size = 0
417
355
        while sent_size < len(client.secret):
418
356
            sent = session.send(client.secret[sent_size:])
419
 
            logger.debug(u"Sent: %d, remaining: %d",
420
 
                         sent, len(client.secret)
421
 
                         - (sent_size + sent))
 
357
            if debug:
 
358
                sys.stderr.write(u"Sent: %d, remaining: %d\n"
 
359
                                 % (sent, len(client.secret)
 
360
                                    - (sent_size + sent)))
422
361
            sent_size += sent
423
362
        session.bye()
424
363
 
452
391
                                       self.options.interface)
453
392
            except socket.error, error:
454
393
                if error[0] == errno.EPERM:
455
 
                    logger.warning(u"No permission to"
456
 
                                   u" bind to interface %s",
457
 
                                   self.options.interface)
 
394
                    sys.stderr.write(u"Warning: No permission to" \
 
395
                                     u" bind to interface %s\n"
 
396
                                     % self.options.interface)
458
397
                else:
459
398
                    raise error
460
399
        # Only bind(2) the socket if we really need to.
504
443
 
505
444
 
506
445
def add_service():
507
 
    """Derived from the Avahi example code"""
 
446
    """From the Avahi server example code"""
508
447
    global group, serviceName, serviceType, servicePort, serviceTXT, \
509
448
           domain, host
510
449
    if group is None:
514
453
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
515
454
        group.connect_to_signal('StateChanged',
516
455
                                entry_group_state_changed)
517
 
    logger.debug(u"Adding service '%s' of type '%s' ...",
518
 
                 serviceName, serviceType)
 
456
    if debug:
 
457
        sys.stderr.write(u"Adding service '%s' of type '%s' ...\n"
 
458
                         % (serviceName, serviceType))
519
459
    
520
460
    group.AddService(
521
461
            serviceInterface,           # interface
529
469
 
530
470
 
531
471
def remove_service():
532
 
    """From the Avahi example code"""
 
472
    """From the Avahi server example code"""
533
473
    global group
534
474
    
535
475
    if not group is None:
537
477
 
538
478
 
539
479
def server_state_changed(state):
540
 
    """Derived from the Avahi example code"""
 
480
    """From the Avahi server example code"""
541
481
    if state == avahi.SERVER_COLLISION:
542
 
        logger.warning(u"Server name collision")
 
482
        sys.stderr.write(u"WARNING: Server name collision\n")
543
483
        remove_service()
544
484
    elif state == avahi.SERVER_RUNNING:
545
485
        add_service()
546
486
 
547
487
 
548
488
def entry_group_state_changed(state, error):
549
 
    """Derived from the Avahi example code"""
 
489
    """From the Avahi server example code"""
550
490
    global serviceName, server, rename_count
551
491
    
552
 
    logger.debug(u"state change: %i", state)
 
492
    if debug:
 
493
        sys.stderr.write(u"state change: %i\n" % state)
553
494
    
554
495
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
555
 
        logger.debug(u"Service established.")
 
496
        if debug:
 
497
            sys.stderr.write(u"Service established.\n")
556
498
    elif state == avahi.ENTRY_GROUP_COLLISION:
557
499
        
558
500
        rename_count = rename_count - 1
559
501
        if rename_count > 0:
560
502
            name = server.GetAlternativeServiceName(name)
561
 
            logger.warning(u"Service name collision, "
562
 
                           u"changing name to '%s' ...", name)
 
503
            sys.stderr.write(u"WARNING: Service name collision, "
 
504
                             u"changing name to '%s' ...\n" % name)
563
505
            remove_service()
564
506
            add_service()
565
507
            
566
508
        else:
567
 
            logger.error(u"No suitable service name found after %i"
568
 
                         u" retries, exiting.", n_rename)
569
 
            killme(1)
 
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()
570
513
    elif state == avahi.ENTRY_GROUP_FAILURE:
571
 
        logger.error(u"Error in group state changed %s",
572
 
                     unicode(error))
573
 
        killme(1)
 
514
        sys.stderr.write(u"Error in group state changed %s\n"
 
515
                         % unicode(error))
 
516
        main_loop.quit()
 
517
        return
574
518
 
575
519
 
576
520
def if_nametoindex(interface):
592
536
        return interface_index
593
537
 
594
538
 
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
 
    
 
539
if __name__ == '__main__':
631
540
    parser = OptionParser()
632
541
    parser.add_option("-i", "--interface", type="string",
633
542
                      default=None, metavar="IF",
634
543
                      help="Bind to interface IF")
635
 
    parser.add_option("-a", "--address", type="string", default=None,
636
 
                      help="Address to listen for requests on")
 
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")
637
556
    parser.add_option("-p", "--port", type="int", default=None,
638
557
                      help="Port number to receive requests on")
639
558
    parser.add_option("--timeout", type="string", # Parsed later
664
583
        parser.error("option --interval: Unparseable time")
665
584
    
666
585
    # Parse config file
667
 
    defaults = { "checker": "fping -q -- %%(fqdn)s" }
 
586
    defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
668
587
    client_config = ConfigParser.SafeConfigParser(defaults)
669
 
    #client_config.readfp(open("global.conf"), "global.conf")
 
588
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
670
589
    client_config.read("mandos-clients.conf")
671
590
    
672
 
    global main_loop
673
 
    global bus
674
 
    global server
675
 
    # From the Avahi example code
 
591
    # From the Avahi server example code
676
592
    DBusGMainLoop(set_as_default=True )
677
593
    main_loop = gobject.MainLoop()
678
594
    bus = dbus.SystemBus()
683
599
    
684
600
    debug = options.debug
685
601
    
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
 
    
694
602
    clients = Set()
695
603
    def remove_from_clients(client):
696
604
        clients.remove(client)
697
605
        if not clients:
698
 
            logger.debug(u"No clients left, exiting")
699
 
            killme()
 
606
            if debug:
 
607
                sys.stderr.write(u"No clients left, exiting\n")
 
608
            main_loop.quit()
700
609
    
701
610
    clients.update(Set(Client(name=section, options=options,
702
611
                              stop_hook = remove_from_clients,
703
612
                              **(dict(client_config\
704
613
                                      .items(section))))
705
614
                       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
 
    
731
615
    for client in clients:
732
616
        client.start()
733
617
    
734
 
    tcp_server = IPv6_TCPServer((options.address, options.port),
 
618
    tcp_server = IPv6_TCPServer((None, options.port),
735
619
                                tcp_handler,
736
620
                                options=options,
737
621
                                clients=clients)
738
622
    # Find out what random port we got
739
 
    global servicePort
740
623
    servicePort = tcp_server.socket.getsockname()[1]
741
 
    logger.debug(u"Now listening on port %d", servicePort)
 
624
    if debug:
 
625
        sys.stderr.write(u"Now listening on port %d\n" % servicePort)
742
626
    
743
627
    if options.interface is not None:
744
 
        global serviceInterface
745
628
        serviceInterface = if_nametoindex(options.interface)
746
629
    
747
 
    # From the Avahi example code
 
630
    # From the Avahi server example code
748
631
    server.connect_to_signal("StateChanged", server_state_changed)
749
 
    try:
750
 
        server_state_changed(server.GetState())
751
 
    except dbus.exceptions.DBusException, error:
752
 
        logger.critical(u"DBusException: %s", error)
753
 
        killme(1)
 
632
    server_state_changed(server.GetState())
754
633
    # End of Avahi example code
755
634
    
756
635
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
758
637
                         tcp_server.handle_request(*args[2:],
759
638
                                                   **kwargs) or True)
760
639
    try:
761
 
        logger.debug("Starting main loop")
762
 
        main_loop_started = True
763
640
        main_loop.run()
764
641
    except KeyboardInterrupt:
765
 
        if debug:
766
 
            print
 
642
        print
767
643
    
768
 
    sys.exit(exitstatus)
 
644
    # Cleanup here
769
645
 
770
 
if __name__ == '__main__':
771
 
    main()
 
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()