/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« 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:
90
 
serviceName = None
 
35
# From the Avahi server example code:
 
36
serviceName = "Mandos"
91
37
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
38
servicePort = None                      # Not known at startup
93
39
serviceTXT = []                         # TXT record for the service
152
98
    interval = property(lambda self: self._interval,
153
99
                        _set_interval)
154
100
    del _set_interval
155
 
    def __init__(self, name=None, stop_hook=None, fingerprint=None,
156
 
                 secret=None, secfile=None, fqdn=None, timeout=None,
157
 
                 interval=-1, checker=None):
158
 
        """Note: the 'checker' argument sets the 'checker_command'
159
 
        attribute and not the 'checker' attribute.."""
 
101
    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):
160
104
        self.name = name
161
 
        logger.debug(u"Creating client %r", self.name)
162
105
        # Uppercase and remove spaces from fingerprint
163
106
        # for later comparison purposes with return value of
164
107
        # the fingerprint() function
165
108
        self.fingerprint = fingerprint.upper().replace(u" ", u"")
166
 
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
167
109
        if secret:
168
110
            self.secret = secret.decode(u"base64")
169
111
        elif secfile:
176
118
        self.fqdn = fqdn                # string
177
119
        self.created = datetime.datetime.now()
178
120
        self.last_seen = None
179
 
        self.timeout = string_to_delta(timeout)
180
 
        self.interval = string_to_delta(interval)
 
121
        if timeout is None:
 
122
            timeout = options.timeout
 
123
        self.timeout = timeout
 
124
        if interval == -1:
 
125
            interval = options.interval
 
126
        else:
 
127
            interval = string_to_delta(interval)
 
128
        self.interval = interval
181
129
        self.stop_hook = stop_hook
182
130
        self.checker = None
183
131
        self.checker_initiator_tag = None
185
133
        self.checker_callback_tag = None
186
134
        self.check_command = checker
187
135
    def start(self):
188
 
        """Start this client's checker and timeout hooks"""
 
136
        """Start this clients checker and timeout hooks"""
189
137
        # Schedule a new checker to be started an 'interval' from now,
190
138
        # and every interval from then on.
191
139
        self.checker_initiator_tag = gobject.timeout_add\
201
149
        """Stop this client.
202
150
        The possibility that this client might be restarted is left
203
151
        open, but not currently used."""
204
 
        # If this client doesn't have a secret, it is already stopped.
205
 
        if self.secret:
206
 
            logger.debug(u"Stopping client %s", self.name)
207
 
            self.secret = None
208
 
        else:
209
 
            return False
210
 
        if hasattr(self, "stop_initiator_tag") \
211
 
               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:
212
156
            gobject.source_remove(self.stop_initiator_tag)
213
157
            self.stop_initiator_tag = None
214
 
        if hasattr(self, "checker_initiator_tag") \
215
 
               and self.checker_initiator_tag:
 
158
        if self.checker_initiator_tag:
216
159
            gobject.source_remove(self.checker_initiator_tag)
217
160
            self.checker_initiator_tag = None
218
161
        self.stop_checker()
221
164
        # Do not run this again if called by a gobject.timeout_add
222
165
        return False
223
166
    def __del__(self):
224
 
        self.stop_hook = None
225
 
        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()
226
177
    def checker_callback(self, pid, condition):
227
178
        """The checker has completed, so take appropriate actions."""
228
179
        now = datetime.datetime.now()
229
 
        self.checker_callback_tag = None
230
 
        self.checker = None
231
180
        if os.WIFEXITED(condition) \
232
181
               and (os.WEXITSTATUS(condition) == 0):
233
 
            logger.debug(u"Checker for %(name)s succeeded",
234
 
                         vars(self))
 
182
            if debug:
 
183
                sys.stderr.write(u"Checker for %(name)s succeeded\n"
 
184
                                 % vars(self))
235
185
            self.last_seen = now
236
186
            gobject.source_remove(self.stop_initiator_tag)
237
187
            self.stop_initiator_tag = gobject.timeout_add\
238
188
                                      (self._timeout_milliseconds,
239
189
                                       self.stop)
240
 
        elif not os.WIFEXITED(condition):
241
 
            logger.warning(u"Checker for %(name)s crashed?",
242
 
                           vars(self))
243
 
        else:
244
 
            logger.debug(u"Checker for %(name)s failed",
245
 
                         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
246
199
    def start_checker(self):
247
200
        """Start a new checker subprocess if one is not running.
248
201
        If a checker already exists, leave it running and do
249
202
        nothing."""
250
 
        # The reason for not killing a running checker is that if we
251
 
        # did that, then if a checker (for some reason) started
252
 
        # running slowly and taking more than 'interval' time, the
253
 
        # client would inevitably timeout, since no checker would get
254
 
        # a chance to run to completion.  If we instead leave running
255
 
        # checkers alone, the checker would have to take more time
256
 
        # than 'timeout' for the client to be declared invalid, which
257
 
        # is as it should be.
258
203
        if self.checker is None:
 
204
            if debug:
 
205
                sys.stderr.write(u"Starting checker for %s\n"
 
206
                                 % self.name)
259
207
            try:
260
208
                command = self.check_command % self.fqdn
261
209
            except TypeError:
262
210
                escaped_attrs = dict((key, re.escape(str(val)))
263
211
                                     for key, val in
264
212
                                     vars(self).iteritems())
265
 
                try:
266
 
                    command = self.check_command % escaped_attrs
267
 
                except TypeError, error:
268
 
                    logger.critical(u'Could not format string "%s":'
269
 
                                    u' %s', self.check_command, error)
270
 
                    return True # Try again later
 
213
                command = self.check_command % escaped_attrs
271
214
            try:
272
 
                logger.debug(u"Starting checker %r for %s",
273
 
                             command, self.name)
274
215
                self.checker = subprocess.\
275
216
                               Popen(command,
 
217
                                     stdout=subprocess.PIPE,
276
218
                                     close_fds=True, shell=True,
277
219
                                     cwd="/")
278
 
                self.checker_callback_tag = gobject.child_watch_add\
279
 
                                            (self.checker.pid,
280
 
                                             self.checker_callback)
 
220
                self.checker_callback_tag = gobject.\
 
221
                                            child_watch_add(self.checker.pid,
 
222
                                                            self.\
 
223
                                                            checker_callback)
281
224
            except subprocess.OSError, error:
282
 
                logger.error(u"Failed to start subprocess: %s",
283
 
                             error)
 
225
                sys.stderr.write(u"Failed to start subprocess: %s\n"
 
226
                                 % error)
284
227
        # Re-run this periodically if run by gobject.timeout_add
285
228
        return True
286
229
    def stop_checker(self):
287
230
        """Force the checker process, if any, to stop."""
288
 
        if self.checker_callback_tag:
289
 
            gobject.source_remove(self.checker_callback_tag)
290
 
            self.checker_callback_tag = None
291
231
        if not hasattr(self, "checker") or self.checker is None:
292
232
            return
293
 
        logger.debug("Stopping checker for %(name)s", vars(self))
294
 
        try:
295
 
            os.kill(self.checker.pid, signal.SIGTERM)
296
 
            #os.sleep(0.5)
297
 
            #if self.checker.poll() is None:
298
 
            #    os.kill(self.checker.pid, signal.SIGKILL)
299
 
        except OSError, error:
300
 
            if error.errno != errno.ESRCH:
301
 
                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)
302
238
        self.checker = None
303
239
    def still_valid(self, now=None):
304
240
        """Has the timeout not yet passed for this client?"""
311
247
 
312
248
 
313
249
def peer_certificate(session):
314
 
    "Return the peer's OpenPGP certificate as a bytestring"
315
250
    # If not an OpenPGP certificate...
316
251
    if gnutls.library.functions.gnutls_certificate_type_get\
317
252
            (session._c_object) \
328
263
 
329
264
 
330
265
def fingerprint(openpgp):
331
 
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
332
266
    # New empty GnuTLS certificate
333
267
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
334
268
    gnutls.library.functions.gnutls_openpgp_crt_init\
364
298
    Note: This will run in its own forked process."""
365
299
    
366
300
    def handle(self):
367
 
        logger.debug(u"TCP connection from: %s",
368
 
                     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)
369
307
        session = gnutls.connection.ClientSession(self.request,
370
308
                                                  gnutls.connection.\
371
309
                                                  X509Credentials())
373
311
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
374
312
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
375
313
        #                "+DHE-DSS"))
376
 
        priority = "NORMAL"
377
 
        if self.server.options.priority:
378
 
            priority = self.server.options.priority
 
314
        priority = "SECURE256"
 
315
        
379
316
        gnutls.library.functions.gnutls_priority_set_direct\
380
317
            (session._c_object, priority, None);
381
318
        
382
319
        try:
383
320
            session.handshake()
384
321
        except gnutls.errors.GNUTLSError, error:
385
 
            logger.debug(u"Handshake failed: %s", error)
 
322
            if debug:
 
323
                sys.stderr.write(u"Handshake failed: %s\n" % error)
386
324
            # Do not run session.bye() here: the session is not
387
325
            # established.  Just abandon the request.
388
326
            return
389
327
        try:
390
328
            fpr = fingerprint(peer_certificate(session))
391
329
        except (TypeError, gnutls.errors.GNUTLSError), error:
392
 
            logger.debug(u"Bad certificate: %s", error)
 
330
            if debug:
 
331
                sys.stderr.write(u"Bad certificate: %s\n" % error)
393
332
            session.bye()
394
333
            return
395
 
        logger.debug(u"Fingerprint: %s", fpr)
 
334
        if debug:
 
335
            sys.stderr.write(u"Fingerprint: %s\n" % fpr)
396
336
        client = None
397
 
        for c in self.server.clients:
 
337
        for c in clients:
398
338
            if c.fingerprint == fpr:
399
339
                client = c
400
340
                break
402
342
        # that the client timed out while establishing the GnuTLS
403
343
        # session.
404
344
        if (not client) or (not client.still_valid()):
405
 
            if client:
406
 
                logger.debug(u"Client %(name)s is invalid",
407
 
                             vars(client))
408
 
            else:
409
 
                logger.debug(u"Client not found for fingerprint: %s",
410
 
                             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)
411
352
            session.bye()
412
353
            return
413
354
        sent_size = 0
414
355
        while sent_size < len(client.secret):
415
356
            sent = session.send(client.secret[sent_size:])
416
 
            logger.debug(u"Sent: %d, remaining: %d",
417
 
                         sent, len(client.secret)
418
 
                         - (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)))
419
361
            sent_size += sent
420
362
        session.bye()
421
363
 
449
391
                                       self.options.interface)
450
392
            except socket.error, error:
451
393
                if error[0] == errno.EPERM:
452
 
                    logger.warning(u"No permission to"
453
 
                                   u" bind to interface %s",
454
 
                                   self.options.interface)
 
394
                    sys.stderr.write(u"Warning: No permission to" \
 
395
                                     u" bind to interface %s\n"
 
396
                                     % self.options.interface)
455
397
                else:
456
398
                    raise error
457
399
        # Only bind(2) the socket if we really need to.
501
443
 
502
444
 
503
445
def add_service():
504
 
    """Derived from the Avahi example code"""
 
446
    """From the Avahi server example code"""
505
447
    global group, serviceName, serviceType, servicePort, serviceTXT, \
506
448
           domain, host
507
449
    if group is None:
511
453
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
512
454
        group.connect_to_signal('StateChanged',
513
455
                                entry_group_state_changed)
514
 
    logger.debug(u"Adding service '%s' of type '%s' ...",
515
 
                 serviceName, serviceType)
 
456
    if debug:
 
457
        sys.stderr.write(u"Adding service '%s' of type '%s' ...\n"
 
458
                         % (serviceName, serviceType))
516
459
    
517
460
    group.AddService(
518
461
            serviceInterface,           # interface
526
469
 
527
470
 
528
471
def remove_service():
529
 
    """From the Avahi example code"""
 
472
    """From the Avahi server example code"""
530
473
    global group
531
474
    
532
475
    if not group is None:
534
477
 
535
478
 
536
479
def server_state_changed(state):
537
 
    """Derived from the Avahi example code"""
 
480
    """From the Avahi server example code"""
538
481
    if state == avahi.SERVER_COLLISION:
539
 
        logger.warning(u"Server name collision")
 
482
        sys.stderr.write(u"WARNING: Server name collision\n")
540
483
        remove_service()
541
484
    elif state == avahi.SERVER_RUNNING:
542
485
        add_service()
543
486
 
544
487
 
545
488
def entry_group_state_changed(state, error):
546
 
    """Derived from the Avahi example code"""
 
489
    """From the Avahi server example code"""
547
490
    global serviceName, server, rename_count
548
491
    
549
 
    logger.debug(u"state change: %i", state)
 
492
    if debug:
 
493
        sys.stderr.write(u"state change: %i\n" % state)
550
494
    
551
495
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
552
 
        logger.debug(u"Service established.")
 
496
        if debug:
 
497
            sys.stderr.write(u"Service established.\n")
553
498
    elif state == avahi.ENTRY_GROUP_COLLISION:
554
499
        
555
500
        rename_count = rename_count - 1
556
501
        if rename_count > 0:
557
502
            name = server.GetAlternativeServiceName(name)
558
 
            logger.warning(u"Service name collision, "
559
 
                           u"changing name to '%s' ...", name)
 
503
            sys.stderr.write(u"WARNING: Service name collision, "
 
504
                             u"changing name to '%s' ...\n" % name)
560
505
            remove_service()
561
506
            add_service()
562
507
            
563
508
        else:
564
 
            logger.error(u"No suitable service name found after %i"
565
 
                         u" retries, exiting.", n_rename)
566
 
            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()
567
513
    elif state == avahi.ENTRY_GROUP_FAILURE:
568
 
        logger.error(u"Error in group state changed %s",
569
 
                     unicode(error))
570
 
        killme(1)
 
514
        sys.stderr.write(u"Error in group state changed %s\n"
 
515
                         % unicode(error))
 
516
        main_loop.quit()
 
517
        return
571
518
 
572
519
 
573
520
def if_nametoindex(interface):
589
536
        return interface_index
590
537
 
591
538
 
592
 
def daemon(nochdir, noclose):
593
 
    """See daemon(3).  Standard BSD Unix function.
594
 
    This should really exist as os.daemon, but it doesn't (yet)."""
595
 
    if os.fork():
596
 
        sys.exit()
597
 
    os.setsid()
598
 
    if not nochdir:
599
 
        os.chdir("/")
600
 
    if not noclose:
601
 
        # Close all standard open file descriptors
602
 
        null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
603
 
        if not stat.S_ISCHR(os.fstat(null).st_mode):
604
 
            raise OSError(errno.ENODEV,
605
 
                          "/dev/null not a character device")
606
 
        os.dup2(null, sys.stdin.fileno())
607
 
        os.dup2(null, sys.stdout.fileno())
608
 
        os.dup2(null, sys.stderr.fileno())
609
 
        if null > 2:
610
 
            os.close(null)
611
 
 
612
 
 
613
 
def killme(status = 0):
614
 
    logger.debug("Stopping server with exit status %d", status)
615
 
    exitstatus = status
616
 
    if main_loop_started:
617
 
        main_loop.quit()
618
 
    else:
619
 
        sys.exit(status)
620
 
 
621
 
 
622
 
def main():
623
 
    global exitstatus
624
 
    exitstatus = 0
625
 
    global main_loop_started
626
 
    main_loop_started = False
627
 
    
 
539
if __name__ == '__main__':
628
540
    parser = OptionParser()
629
541
    parser.add_option("-i", "--interface", type="string",
630
542
                      default=None, metavar="IF",
631
543
                      help="Bind to interface IF")
632
 
    parser.add_option("-a", "--address", type="string", default=None,
633
 
                      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")
634
556
    parser.add_option("-p", "--port", type="int", default=None,
635
557
                      help="Port number to receive requests on")
 
558
    parser.add_option("--timeout", type="string", # Parsed later
 
559
                      default="1h",
 
560
                      help="Amount of downtime allowed for clients")
 
561
    parser.add_option("--interval", type="string", # Parsed later
 
562
                      default="5m",
 
563
                      help="How often to check that a client is up")
636
564
    parser.add_option("--check", action="store_true", default=False,
637
565
                      help="Run self-test")
638
566
    parser.add_option("--debug", action="store_true", default=False,
639
567
                      help="Debug mode")
640
 
    parser.add_option("--priority", type="string",
641
 
                      default="SECURE256",
642
 
                      help="GnuTLS priority string"
643
 
                      " (see GnuTLS documentation)")
644
 
    parser.add_option("--servicename", type="string",
645
 
                      default="Mandos", help="Zeroconf service name")
646
568
    (options, args) = parser.parse_args()
647
569
    
648
570
    if options.check:
650
572
        doctest.testmod()
651
573
        sys.exit()
652
574
    
 
575
    # Parse the time arguments
 
576
    try:
 
577
        options.timeout = string_to_delta(options.timeout)
 
578
    except ValueError:
 
579
        parser.error("option --timeout: Unparseable time")
 
580
    try:
 
581
        options.interval = string_to_delta(options.interval)
 
582
    except ValueError:
 
583
        parser.error("option --interval: Unparseable time")
 
584
    
653
585
    # Parse config file
654
 
    defaults = { "timeout": "1h",
655
 
                 "interval": "5m",
656
 
                 "checker": "fping -q -- %%(fqdn)s",
657
 
                 }
 
586
    defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
658
587
    client_config = ConfigParser.SafeConfigParser(defaults)
659
 
    #client_config.readfp(open("global.conf"), "global.conf")
 
588
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
660
589
    client_config.read("mandos-clients.conf")
661
590
    
662
 
    global serviceName
663
 
    serviceName = options.servicename;
664
 
    
665
 
    global main_loop
666
 
    global bus
667
 
    global server
668
 
    # From the Avahi example code
 
591
    # From the Avahi server example code
669
592
    DBusGMainLoop(set_as_default=True )
670
593
    main_loop = gobject.MainLoop()
671
594
    bus = dbus.SystemBus()
676
599
    
677
600
    debug = options.debug
678
601
    
679
 
    if debug:
680
 
        console = logging.StreamHandler()
681
 
        # console.setLevel(logging.DEBUG)
682
 
        console.setFormatter(logging.Formatter\
683
 
                             ('%(levelname)s: %(message)s'))
684
 
        logger.addHandler(console)
685
 
        del console
686
 
    
687
602
    clients = Set()
688
603
    def remove_from_clients(client):
689
604
        clients.remove(client)
690
605
        if not clients:
691
 
            logger.debug(u"No clients left, exiting")
692
 
            killme()
 
606
            if debug:
 
607
                sys.stderr.write(u"No clients left, exiting\n")
 
608
            main_loop.quit()
693
609
    
694
 
    clients.update(Set(Client(name=section,
 
610
    clients.update(Set(Client(name=section, options=options,
695
611
                              stop_hook = remove_from_clients,
696
612
                              **(dict(client_config\
697
613
                                      .items(section))))
698
614
                       for section in client_config.sections()))
699
 
    
700
 
    if not debug:
701
 
        daemon(False, False)
702
 
    
703
 
    def cleanup():
704
 
        "Cleanup function; run on exit"
705
 
        global group
706
 
        # From the Avahi example code
707
 
        if not group is None:
708
 
            group.Free()
709
 
            group = None
710
 
        # End of Avahi example code
711
 
        
712
 
        while clients:
713
 
            client = clients.pop()
714
 
            client.stop_hook = None
715
 
            client.stop()
716
 
    
717
 
    atexit.register(cleanup)
718
 
    
719
 
    if not debug:
720
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
721
 
    signal.signal(signal.SIGHUP, lambda signum, frame: killme())
722
 
    signal.signal(signal.SIGTERM, lambda signum, frame: killme())
723
 
    
724
615
    for client in clients:
725
616
        client.start()
726
617
    
727
 
    tcp_server = IPv6_TCPServer((options.address, options.port),
 
618
    tcp_server = IPv6_TCPServer((None, options.port),
728
619
                                tcp_handler,
729
620
                                options=options,
730
621
                                clients=clients)
731
622
    # Find out what random port we got
732
 
    global servicePort
733
623
    servicePort = tcp_server.socket.getsockname()[1]
734
 
    logger.debug(u"Now listening on port %d", servicePort)
 
624
    if debug:
 
625
        sys.stderr.write(u"Now listening on port %d\n" % servicePort)
735
626
    
736
627
    if options.interface is not None:
737
 
        global serviceInterface
738
628
        serviceInterface = if_nametoindex(options.interface)
739
629
    
740
 
    # From the Avahi example code
 
630
    # From the Avahi server example code
741
631
    server.connect_to_signal("StateChanged", server_state_changed)
742
 
    try:
743
 
        server_state_changed(server.GetState())
744
 
    except dbus.exceptions.DBusException, error:
745
 
        logger.critical(u"DBusException: %s", error)
746
 
        killme(1)
 
632
    server_state_changed(server.GetState())
747
633
    # End of Avahi example code
748
634
    
749
635
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
751
637
                         tcp_server.handle_request(*args[2:],
752
638
                                                   **kwargs) or True)
753
639
    try:
754
 
        logger.debug("Starting main loop")
755
 
        main_loop_started = True
756
640
        main_loop.run()
757
641
    except KeyboardInterrupt:
758
 
        if debug:
759
 
            print
 
642
        print
760
643
    
761
 
    sys.exit(exitstatus)
 
644
    # Cleanup here
762
645
 
763
 
if __name__ == '__main__':
764
 
    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()