/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-20 23:34:51 UTC
  • mto: This revision was merged to the branch mainline in revision 17.
  • Revision ID: belorn@braxen-20080720233451-720put8qyxqvk2ld
Added debug options from passprompt as --debug and --debug=passprompt

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
 
2
# -*- mode: python; coding: utf-8 -*-
2
3
 
3
4
from __future__ import division
4
5
 
11
12
import gnutls.crypto
12
13
import gnutls.connection
13
14
import gnutls.errors
 
15
import gnutls.library.functions
 
16
import gnutls.library.constants
 
17
import gnutls.library.types
14
18
import ConfigParser
15
19
import sys
16
20
import re
18
22
import signal
19
23
from sets import Set
20
24
import subprocess
 
25
import atexit
 
26
import stat
21
27
 
22
28
import dbus
23
29
import gobject
24
30
import avahi
25
31
from dbus.mainloop.glib import DBusGMainLoop
26
 
 
27
 
# This variable is used to optionally bind to a specified
28
 
# interface.
 
32
import ctypes
 
33
 
 
34
import logging
 
35
import logging.handlers
 
36
 
 
37
logger = logging.Logger('mandos')
 
38
syslogger = logging.handlers.SysLogHandler\
 
39
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON)
 
40
syslogger.setFormatter(logging.Formatter\
 
41
                        ('%(levelname)s: %(message)s'))
 
42
logger.addHandler(syslogger)
 
43
del syslogger
 
44
 
 
45
# This variable is used to optionally bind to a specified interface.
 
46
# It is a global variable to fit in with the other variables from the
 
47
# Avahi server example code.
29
48
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:
 
49
# From the Avahi server example code:
32
50
serviceName = "Mandos"
33
51
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
34
52
servicePort = None                      # Not known at startup
44
62
class Client(object):
45
63
    """A representation of a client host served by this server.
46
64
    Attributes:
47
 
    password:  string
48
 
    fqdn:      string, FQDN (used by the checker)
 
65
    name:      string; from the config file, used in log messages
 
66
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
67
                 uniquely identify the client
 
68
    secret:    bytestring; sent verbatim (over TLS) to client
 
69
    fqdn:      string (FQDN); available for use by the checker command
49
70
    created:   datetime.datetime()
50
71
    last_seen: datetime.datetime() or None if not yet seen
51
72
    timeout:   datetime.timedelta(); How long from last_seen until
52
73
                                     this client is invalid
53
74
    interval:  datetime.timedelta(); How often to start a new checker
54
 
    timeout_milliseconds: Used by gobject.timeout_add()
55
 
    interval_milliseconds: - '' -
56
75
    stop_hook: If set, called by stop() as stop_hook(self)
57
76
    checker:   subprocess.Popen(); a running checker process used
58
77
                                   to see if the client lives.
60
79
    checker_initiator_tag: a gobject event source tag, or None
61
80
    stop_initiator_tag:    - '' -
62
81
    checker_callback_tag:  - '' -
 
82
    checker_command: string; External command which is run to check if
 
83
                     client lives.  %()s expansions are done at
 
84
                     runtime with vars(self) as dict, so that for
 
85
                     instance %(name)s can be used in the command.
 
86
    Private attibutes:
 
87
    _timeout: Real variable for 'timeout'
 
88
    _interval: Real variable for 'interval'
 
89
    _timeout_milliseconds: Used by gobject.timeout_add()
 
90
    _interval_milliseconds: - '' -
63
91
    """
 
92
    def _set_timeout(self, timeout):
 
93
        "Setter function for 'timeout' attribute"
 
94
        self._timeout = timeout
 
95
        self._timeout_milliseconds = ((self.timeout.days
 
96
                                       * 24 * 60 * 60 * 1000)
 
97
                                      + (self.timeout.seconds * 1000)
 
98
                                      + (self.timeout.microseconds
 
99
                                         // 1000))
 
100
    timeout = property(lambda self: self._timeout,
 
101
                       _set_timeout)
 
102
    del _set_timeout
 
103
    def _set_interval(self, interval):
 
104
        "Setter function for 'interval' attribute"
 
105
        self._interval = interval
 
106
        self._interval_milliseconds = ((self.interval.days
 
107
                                        * 24 * 60 * 60 * 1000)
 
108
                                       + (self.interval.seconds
 
109
                                          * 1000)
 
110
                                       + (self.interval.microseconds
 
111
                                          // 1000))
 
112
    interval = property(lambda self: self._interval,
 
113
                        _set_interval)
 
114
    del _set_interval
64
115
    def __init__(self, name=None, options=None, stop_hook=None,
65
 
                 dn=None, password=None, passfile=None, fqdn=None,
66
 
                 timeout=None, interval=-1):
 
116
                 fingerprint=None, secret=None, secfile=None,
 
117
                 fqdn=None, timeout=None, interval=-1, checker=None):
67
118
        self.name = name
68
 
        self.dn = dn
69
 
        if password:
70
 
            self.password = password
71
 
        elif passfile:
72
 
            self.password = open(passfile).readall()
 
119
        # Uppercase and remove spaces from fingerprint
 
120
        # for later comparison purposes with return value of
 
121
        # the fingerprint() function
 
122
        self.fingerprint = fingerprint.upper().replace(u" ", u"")
 
123
        if secret:
 
124
            self.secret = secret.decode(u"base64")
 
125
        elif secfile:
 
126
            sf = open(secfile)
 
127
            self.secret = sf.read()
 
128
            sf.close()
73
129
        else:
74
 
            raise RuntimeError(u"No Password or Passfile for client %s"
 
130
            raise RuntimeError(u"No secret or secfile for client %s"
75
131
                               % self.name)
76
132
        self.fqdn = fqdn                # string
77
133
        self.created = datetime.datetime.now()
78
134
        self.last_seen = None
79
135
        if timeout is None:
80
 
            timeout = options.timeout
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))
 
136
            self.timeout = options.timeout
 
137
        else:
 
138
            self.timeout = string_to_delta(timeout)
87
139
        if interval == -1:
88
 
            interval = options.interval
 
140
            self.interval = options.interval
89
141
        else:
90
 
            interval = string_to_delta(interval)
91
 
        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))
 
142
            self.interval = string_to_delta(interval)
97
143
        self.stop_hook = stop_hook
98
144
        self.checker = None
99
145
        self.checker_initiator_tag = None
100
146
        self.stop_initiator_tag = None
101
147
        self.checker_callback_tag = None
 
148
        self.check_command = checker
102
149
    def start(self):
103
150
        """Start this clients checker and timeout hooks"""
104
151
        # Schedule a new checker to be started an 'interval' from now,
105
152
        # and every interval from then on.
106
 
        self.checker_initiator_tag = gobject.\
107
 
                                     timeout_add(self.interval_milliseconds,
108
 
                                                 self.start_checker)
 
153
        self.checker_initiator_tag = gobject.timeout_add\
 
154
                                     (self._interval_milliseconds,
 
155
                                      self.start_checker)
109
156
        # Also start a new checker *right now*.
110
157
        self.start_checker()
111
158
        # Schedule a stop() when 'timeout' has passed
112
 
        self.stop_initiator_tag = gobject.\
113
 
                                     timeout_add(self.timeout_milliseconds,
114
 
                                                 self.stop)
 
159
        self.stop_initiator_tag = gobject.timeout_add\
 
160
                                  (self._timeout_milliseconds,
 
161
                                   self.stop)
115
162
    def stop(self):
116
163
        """Stop this client.
117
164
        The possibility that this client might be restarted is left
118
165
        open, but not currently used."""
119
 
        # print "Stopping client", self.name
120
 
        self.password = None
 
166
        logger.debug(u"Stopping client %s", self.name)
 
167
        self.secret = None
121
168
        if self.stop_initiator_tag:
122
169
            gobject.source_remove(self.stop_initiator_tag)
123
170
            self.stop_initiator_tag = None
145
192
        now = datetime.datetime.now()
146
193
        if os.WIFEXITED(condition) \
147
194
               and (os.WEXITSTATUS(condition) == 0):
148
 
            #print "Checker for %(name)s succeeded" % vars(self)
 
195
            logger.debug(u"Checker for %(name)s succeeded",
 
196
                         vars(self))
149
197
            self.last_seen = now
150
198
            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
 
199
            self.stop_initiator_tag = gobject.timeout_add\
 
200
                                      (self._timeout_milliseconds,
 
201
                                       self.stop)
 
202
        elif not os.WIFEXITED(condition):
 
203
            logger.warning(u"Checker for %(name)s crashed?",
 
204
                           vars(self))
 
205
        else:
 
206
            logger.debug(u"Checker for %(name)s failed",
 
207
                         vars(self))
 
208
            self.checker = None
160
209
        self.checker_callback_tag = None
161
210
    def start_checker(self):
162
211
        """Start a new checker subprocess if one is not running.
163
212
        If a checker already exists, leave it running and do
164
213
        nothing."""
165
214
        if self.checker is None:
166
 
            #print "Starting checker for", self.name
167
 
            try:
 
215
            try:
 
216
                command = self.check_command % self.fqdn
 
217
            except TypeError:
 
218
                escaped_attrs = dict((key, re.escape(str(val)))
 
219
                                     for key, val in
 
220
                                     vars(self).iteritems())
 
221
                try:
 
222
                    command = self.check_command % escaped_attrs
 
223
                except TypeError, error:
 
224
                    logger.critical(u'Could not format string "%s":'
 
225
                                    u' %s', self.check_command, error)
 
226
                    return True # Try again later
 
227
            try:
 
228
                logger.debug(u"Starting checker %r for %s",
 
229
                             command, self.name)
168
230
                self.checker = subprocess.\
169
 
                               Popen("sleep 1; fping -q -- %s"
170
 
                                     % re.escape(self.fqdn),
171
 
                                     stdout=subprocess.PIPE,
 
231
                               Popen(command,
172
232
                                     close_fds=True, shell=True,
173
233
                                     cwd="/")
174
 
                self.checker_callback_tag = gobject.\
175
 
                                            child_watch_add(self.checker.pid,
176
 
                                                            self.\
177
 
                                                            checker_callback)
 
234
                self.checker_callback_tag = gobject.child_watch_add\
 
235
                                            (self.checker.pid,
 
236
                                             self.checker_callback)
178
237
            except subprocess.OSError, error:
179
 
                sys.stderr.write(u"Failed to start subprocess: %s\n"
180
 
                                 % error)
 
238
                logger.error(u"Failed to start subprocess: %s",
 
239
                             error)
181
240
        # Re-run this periodically if run by gobject.timeout_add
182
241
        return True
183
242
    def stop_checker(self):
200
259
            return now < (self.last_seen + self.timeout)
201
260
 
202
261
 
 
262
def peer_certificate(session):
 
263
    "Return an OpenPGP data packet string for the peer's certificate"
 
264
    # If not an OpenPGP certificate...
 
265
    if gnutls.library.functions.gnutls_certificate_type_get\
 
266
            (session._c_object) \
 
267
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
 
268
        # ...do the normal thing
 
269
        return session.peer_certificate
 
270
    list_size = ctypes.c_uint()
 
271
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
 
272
        (session._c_object, ctypes.byref(list_size))
 
273
    if list_size.value == 0:
 
274
        return None
 
275
    cert = cert_list[0]
 
276
    return ctypes.string_at(cert.data, cert.size)
 
277
 
 
278
 
 
279
def fingerprint(openpgp):
 
280
    "Convert an OpenPGP data string to a hexdigit fingerprint string"
 
281
    # New empty GnuTLS certificate
 
282
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
283
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
284
        (ctypes.byref(crt))
 
285
    # New GnuTLS "datum" with the OpenPGP public key
 
286
    datum = gnutls.library.types.gnutls_datum_t\
 
287
        (ctypes.cast(ctypes.c_char_p(openpgp),
 
288
                     ctypes.POINTER(ctypes.c_ubyte)),
 
289
         ctypes.c_uint(len(openpgp)))
 
290
    # Import the OpenPGP public key into the certificate
 
291
    ret = gnutls.library.functions.gnutls_openpgp_crt_import\
 
292
        (crt,
 
293
         ctypes.byref(datum),
 
294
         gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
295
    # New buffer for the fingerprint
 
296
    buffer = ctypes.create_string_buffer(20)
 
297
    buffer_length = ctypes.c_size_t()
 
298
    # Get the fingerprint from the certificate into the buffer
 
299
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
 
300
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
 
301
    # Deinit the certificate
 
302
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
303
    # Convert the buffer to a Python bytestring
 
304
    fpr = ctypes.string_at(buffer, buffer_length.value)
 
305
    # Convert the bytestring to hexadecimal notation
 
306
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
307
    return hex_fpr
 
308
 
 
309
 
203
310
class tcp_handler(SocketServer.BaseRequestHandler, object):
204
311
    """A TCP request handler class.
205
312
    Instantiated by IPv6_TCPServer for each request to handle it.
206
313
    Note: This will run in its own forked process."""
 
314
    
207
315
    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
212
 
        session = gnutls.connection.ServerSession(self.request,
213
 
                                                  self.server\
214
 
                                                  .credentials)
 
316
        logger.debug(u"TCP connection from: %s",
 
317
                     unicode(self.client_address))
 
318
        session = gnutls.connection.ClientSession(self.request,
 
319
                                                  gnutls.connection.\
 
320
                                                  X509Credentials())
 
321
        
 
322
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
323
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
324
        #                "+DHE-DSS"))
 
325
        priority = "SECURE256"
 
326
        
 
327
        gnutls.library.functions.gnutls_priority_set_direct\
 
328
            (session._c_object, priority, None);
 
329
        
215
330
        try:
216
331
            session.handshake()
217
332
        except gnutls.errors.GNUTLSError, error:
218
 
            #sys.stderr.write(u"Handshake failed: %s\n" % error)
 
333
            logger.debug(u"Handshake failed: %s", error)
219
334
            # Do not run session.bye() here: the session is not
220
335
            # established.  Just abandon the request.
221
336
            return
222
 
        #if session.peer_certificate:
223
 
        #    print "DN:", session.peer_certificate.subject
224
337
        try:
225
 
            session.verify_peer()
226
 
        except gnutls.errors.CertificateError, error:
227
 
            #sys.stderr.write(u"Verify failed: %s\n" % error)
 
338
            fpr = fingerprint(peer_certificate(session))
 
339
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
340
            logger.debug(u"Bad certificate: %s", error)
228
341
            session.bye()
229
342
            return
 
343
        logger.debug(u"Fingerprint: %s", fpr)
230
344
        client = None
231
345
        for c in clients:
232
 
            if c.dn == session.peer_certificate.subject:
 
346
            if c.fingerprint == fpr:
233
347
                client = c
234
348
                break
235
349
        # Have to check if client.still_valid(), since it is possible
236
350
        # that the client timed out while establishing the GnuTLS
237
351
        # 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
 
352
        if (not client) or (not client.still_valid()):
 
353
            if client:
 
354
                logger.debug(u"Client %(name)s is invalid",
 
355
                             vars(client))
 
356
            else:
 
357
                logger.debug(u"Client not found for fingerprint: %s",
 
358
                             fpr)
 
359
            session.bye()
 
360
            return
 
361
        sent_size = 0
 
362
        while sent_size < len(client.secret):
 
363
            sent = session.send(client.secret[sent_size:])
 
364
            logger.debug(u"Sent: %d, remaining: %d",
 
365
                         sent, len(client.secret)
 
366
                         - (sent_size + sent))
 
367
            sent_size += sent
249
368
        session.bye()
250
369
 
251
370
 
254
373
    Attributes:
255
374
        options:        Command line options
256
375
        clients:        Set() of Client objects
257
 
        credentials:    GnuTLS X.509 credentials
258
376
    """
259
377
    address_family = socket.AF_INET6
260
378
    def __init__(self, *args, **kwargs):
264
382
        if "clients" in kwargs:
265
383
            self.clients = kwargs["clients"]
266
384
            del kwargs["clients"]
267
 
        if "credentials" in kwargs:
268
 
            self.credentials = kwargs["credentials"]
269
 
            del kwargs["credentials"]
270
385
        return super(type(self), self).__init__(*args, **kwargs)
271
386
    def server_bind(self):
272
387
        """This overrides the normal server_bind() function
282
397
                                       self.options.interface)
283
398
            except socket.error, error:
284
399
                if error[0] == errno.EPERM:
285
 
                    sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
286
 
                                     % self.options.interface)
 
400
                    logger.warning(u"No permission to"
 
401
                                   u" bind to interface %s",
 
402
                                   self.options.interface)
287
403
                else:
288
404
                    raise error
289
405
        # Only bind(2) the socket if we really need to.
343
459
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
344
460
        group.connect_to_signal('StateChanged',
345
461
                                entry_group_state_changed)
346
 
    
347
 
    # print "Adding service '%s' of type '%s' ..." % (serviceName,
348
 
    #                                                 serviceType)
 
462
    logger.debug(u"Adding service '%s' of type '%s' ...",
 
463
                 serviceName, serviceType)
349
464
    
350
465
    group.AddService(
351
466
            serviceInterface,           # interface
369
484
def server_state_changed(state):
370
485
    """From the Avahi server example code"""
371
486
    if state == avahi.SERVER_COLLISION:
372
 
        print "WARNING: Server name collision"
 
487
        logger.warning(u"Server name collision")
373
488
        remove_service()
374
489
    elif state == avahi.SERVER_RUNNING:
375
490
        add_service()
379
494
    """From the Avahi server example code"""
380
495
    global serviceName, server, rename_count
381
496
    
382
 
    # print "state change: %i" % state
 
497
    logger.debug(u"state change: %i", state)
383
498
    
384
499
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
385
 
        pass
386
 
        # print "Service established."
 
500
        logger.debug(u"Service established.")
387
501
    elif state == avahi.ENTRY_GROUP_COLLISION:
388
502
        
389
503
        rename_count = rename_count - 1
390
504
        if rename_count > 0:
391
505
            name = server.GetAlternativeServiceName(name)
392
 
            print "WARNING: Service name collision, changing name to '%s' ..." % name
 
506
            logger.warning(u"Service name collision, "
 
507
                           u"changing name to '%s' ...", name)
393
508
            remove_service()
394
509
            add_service()
395
510
            
396
511
        else:
397
 
            print "ERROR: No suitable service name found after %i retries, exiting." % n_rename
398
 
            main_loop.quit()
 
512
            logger.error(u"No suitable service name found after %i"
 
513
                         u" retries, exiting.", n_rename)
 
514
            killme(1)
399
515
    elif state == avahi.ENTRY_GROUP_FAILURE:
400
 
        print "Error in group state changed", error
401
 
        main_loop.quit()
402
 
        return
 
516
        logger.error(u"Error in group state changed %s",
 
517
                     unicode(error))
 
518
        killme(1)
403
519
 
404
520
 
405
521
def if_nametoindex(interface):
406
522
    """Call the C function if_nametoindex()"""
407
523
    try:
408
 
        if "ctypes" not in sys.modules:
409
 
            import ctypes
410
524
        libc = ctypes.cdll.LoadLibrary("libc.so.6")
411
525
        return libc.if_nametoindex(interface)
412
 
    except (ImportError, OSError, AttributeError):
 
526
    except (OSError, AttributeError):
413
527
        if "struct" not in sys.modules:
414
528
            import struct
415
529
        if "fcntl" not in sys.modules:
423
537
        return interface_index
424
538
 
425
539
 
 
540
def daemon(nochdir, noclose):
 
541
    """See daemon(3).  Standard BSD Unix function.
 
542
    This should really exist as os.daemon, but it doesn't (yet)."""
 
543
    if os.fork():
 
544
        sys.exit()
 
545
    os.setsid()
 
546
    if not nochdir:
 
547
        os.chdir("/")
 
548
    if not noclose:
 
549
        # Close all standard open file descriptors
 
550
        null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
 
551
        if not stat.S_ISCHR(os.fstat(null).st_mode):
 
552
            raise OSError(errno.ENODEV,
 
553
                          "/dev/null not a character device")
 
554
        os.dup2(null, sys.stdin.fileno())
 
555
        os.dup2(null, sys.stdout.fileno())
 
556
        os.dup2(null, sys.stderr.fileno())
 
557
        if null > 2:
 
558
            os.close(null)
 
559
 
 
560
 
 
561
def killme(status = 0):
 
562
    logger.debug("Stopping server with exit status %d", status)
 
563
    exitstatus = status
 
564
    if main_loop_started:
 
565
        main_loop.quit()
 
566
    else:
 
567
        sys.exit(status)
 
568
 
 
569
 
426
570
if __name__ == '__main__':
 
571
    exitstatus = 0
 
572
    main_loop_started = False
427
573
    parser = OptionParser()
428
574
    parser.add_option("-i", "--interface", type="string",
429
575
                      default=None, metavar="IF",
430
576
                      help="Bind to interface IF")
431
 
    parser.add_option("--cert", type="string", default="cert.pem",
432
 
                      metavar="FILE",
433
 
                      help="Public key certificate PEM file to use")
434
 
    parser.add_option("--key", type="string", default="key.pem",
435
 
                      metavar="FILE",
436
 
                      help="Private key PEM file to use")
437
 
    parser.add_option("--ca", type="string", default="ca.pem",
438
 
                      metavar="FILE",
439
 
                      help="Certificate Authority certificate PEM file to use")
440
 
    parser.add_option("--crl", type="string", default="crl.pem",
441
 
                      metavar="FILE",
442
 
                      help="Certificate Revokation List PEM file to use")
443
577
    parser.add_option("-p", "--port", type="int", default=None,
444
578
                      help="Port number to receive requests on")
445
579
    parser.add_option("--timeout", type="string", # Parsed later
450
584
                      help="How often to check that a client is up")
451
585
    parser.add_option("--check", action="store_true", default=False,
452
586
                      help="Run self-test")
 
587
    parser.add_option("--debug", action="store_true", default=False,
 
588
                      help="Debug mode")
453
589
    (options, args) = parser.parse_args()
454
590
    
455
591
    if options.check:
467
603
    except ValueError:
468
604
        parser.error("option --interval: Unparseable time")
469
605
    
470
 
    cert = gnutls.crypto.X509Certificate(open(options.cert).read())
471
 
    key = gnutls.crypto.X509PrivateKey(open(options.key).read())
472
 
    ca = gnutls.crypto.X509Certificate(open(options.ca).read())
473
 
    crl = gnutls.crypto.X509CRL(open(options.crl).read())
474
 
    cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
475
 
    
476
606
    # Parse config file
477
 
    defaults = {}
 
607
    defaults = { "checker": "fping -q -- %%(fqdn)s" }
478
608
    client_config = ConfigParser.SafeConfigParser(defaults)
479
609
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
480
610
    client_config.read("mandos-clients.conf")
488
618
            avahi.DBUS_INTERFACE_SERVER )
489
619
    # End of Avahi example code
490
620
    
 
621
    debug = options.debug
 
622
    
 
623
    if debug:
 
624
        console = logging.StreamHandler()
 
625
        # console.setLevel(logging.DEBUG)
 
626
        console.setFormatter(logging.Formatter\
 
627
                             ('%(levelname)s: %(message)s'))
 
628
        logger.addHandler(console)
 
629
        del console
 
630
    
491
631
    clients = Set()
492
632
    def remove_from_clients(client):
493
633
        clients.remove(client)
494
634
        if not clients:
495
 
            print "No clients left, exiting"
496
 
            main_loop.quit()
 
635
            logger.debug(u"No clients left, exiting")
 
636
            killme()
497
637
    
498
638
    clients.update(Set(Client(name=section, options=options,
499
639
                              stop_hook = remove_from_clients,
500
640
                              **(dict(client_config\
501
641
                                      .items(section))))
502
642
                       for section in client_config.sections()))
 
643
    
 
644
    if not debug:
 
645
        daemon(False, False)
 
646
    
 
647
    def cleanup():
 
648
        "Cleanup function; run on exit"
 
649
        global group
 
650
        # From the Avahi server example code
 
651
        if not group is None:
 
652
            group.Free()
 
653
            group = None
 
654
        # End of Avahi example code
 
655
        
 
656
        for client in clients:
 
657
            client.stop_hook = None
 
658
            client.stop()
 
659
    
 
660
    atexit.register(cleanup)
 
661
    
 
662
    if not debug:
 
663
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
664
    signal.signal(signal.SIGHUP, lambda signum, frame: killme())
 
665
    signal.signal(signal.SIGTERM, lambda signum, frame: killme())
 
666
    
503
667
    for client in clients:
504
668
        client.start()
505
669
    
506
670
    tcp_server = IPv6_TCPServer((None, options.port),
507
671
                                tcp_handler,
508
672
                                options=options,
509
 
                                clients=clients,
510
 
                                credentials=cred)
 
673
                                clients=clients)
511
674
    # Find out what random port we got
512
675
    servicePort = tcp_server.socket.getsockname()[1]
513
 
    #sys.stderr.write("Now listening on port %d\n" % servicePort)
 
676
    logger.debug(u"Now listening on port %d", servicePort)
514
677
    
515
678
    if options.interface is not None:
516
679
        serviceInterface = if_nametoindex(options.interface)
517
680
    
518
681
    # From the Avahi server example code
519
682
    server.connect_to_signal("StateChanged", server_state_changed)
520
 
    server_state_changed(server.GetState())
 
683
    try:
 
684
        server_state_changed(server.GetState())
 
685
    except dbus.exceptions.DBusException, error:
 
686
        logger.critical(u"DBusException: %s", error)
 
687
        killme(1)
521
688
    # End of Avahi example code
522
689
    
523
690
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
525
692
                         tcp_server.handle_request(*args[2:],
526
693
                                                   **kwargs) or True)
527
694
    try:
 
695
        main_loop_started = True
528
696
        main_loop.run()
529
697
    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()
 
698
        if debug:
 
699
            print
 
700
    
 
701
    sys.exit(exitstatus)