/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-20 06:33:48 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080720063348-jscgy5p0itrgvlo8
* mandos-clients.conf ([foo]): Uncommented.
  ([foo]/secret): New.
  ([foo]/secfile): Commented out.
  ([foo]/checker): Changed to "fping -q -- %%(fqdn)s".
  ([foo]/timeout): New.

* server.py: New modeline for Python and Emacs.  Set a logging format.
  (Client.__init__): Bug fix: Choose either the value from the options
                     object or pass the argument through string_to_delta
                     for both "timeout" and "interval".
  (Client.checker_callback): Bug fix: Do not log spurious "Checker for
                             <foo> failed" messages.
  (Client.start_checker): Moved "Starting checker" log message down to
                          just before actually starting the subprocess.
                          Do not redirect the subprocesses' stdout to a
                          pipe.
  (peer_certificate, fingerprint): Added docstrings.
  (entry_group_state_changed): Call "killme()" instead of
                               "main_loop.quit()".
  (daemon, killme): New functions.
  (exitstatus, main_loop_started): New global variables.
  (__main__): Removed the "--cert", "--key", "--ca", and "--crl"
              options.  Removed the sleep command from the default
              checker.  Add a console logger in debug mode.  Call
              "killme()" instead of "main_loop.quit()" when there are no
              more clients.  Call "daemon()" if not in debug mode.
              Register "cleanup()" to run at exit.  Ignore some
              signals.  Catch DBusException to detect another running
              server and exit cleanly.  Exit with "exitstatus".
  (cleanup): New function.

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
 
21
22
import signal
22
23
from sets import Set
23
24
import subprocess
 
25
import atexit
 
26
import stat
24
27
 
25
28
import dbus
26
29
import gobject
31
34
import logging
32
35
import logging.handlers
33
36
 
34
 
# logghandler.setFormatter(logging.Formatter('%(levelname)s %(message)s')
35
 
 
36
37
logger = logging.Logger('mandos')
37
 
logger.addHandler(logging.handlers.SysLogHandler(facility = logging.handlers.SysLogHandler.LOG_DAEMON))
 
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
38
44
 
39
45
# This variable is used to optionally bind to a specified interface.
40
46
# It is a global variable to fit in with the other variables from the
107
113
                        _set_interval)
108
114
    del _set_interval
109
115
    def __init__(self, name=None, options=None, stop_hook=None,
110
 
                 fingerprint=None, secret=None, secfile=None, fqdn=None,
111
 
                 timeout=None, interval=-1, checker=None):
 
116
                 fingerprint=None, secret=None, secfile=None,
 
117
                 fqdn=None, timeout=None, interval=-1, checker=None):
112
118
        self.name = name
113
119
        # Uppercase and remove spaces from fingerprint
114
120
        # for later comparison purposes with return value of
127
133
        self.created = datetime.datetime.now()
128
134
        self.last_seen = None
129
135
        if timeout is None:
130
 
            timeout = options.timeout
131
 
        self.timeout = timeout
 
136
            self.timeout = options.timeout
 
137
        else:
 
138
            self.timeout = string_to_delta(timeout)
132
139
        if interval == -1:
133
 
            interval = options.interval
 
140
            self.interval = options.interval
134
141
        else:
135
 
            interval = string_to_delta(interval)
136
 
        self.interval = interval
 
142
            self.interval = string_to_delta(interval)
137
143
        self.stop_hook = stop_hook
138
144
        self.checker = None
139
145
        self.checker_initiator_tag = None
193
199
            self.stop_initiator_tag = gobject.timeout_add\
194
200
                                      (self._timeout_milliseconds,
195
201
                                       self.stop)
196
 
        if not os.WIFEXITED(condition):
 
202
        elif not os.WIFEXITED(condition):
197
203
            logger.warning(u"Checker for %(name)s crashed?",
198
204
                           vars(self))
199
205
        else:
206
212
        If a checker already exists, leave it running and do
207
213
        nothing."""
208
214
        if self.checker is None:
209
 
            logger.debug(u"Starting checker for %s",
210
 
                         self.name)
211
215
            try:
212
216
                command = self.check_command % self.fqdn
213
217
            except TypeError:
217
221
                try:
218
222
                    command = self.check_command % escaped_attrs
219
223
                except TypeError, error:
220
 
                    logger.critical(u'Could not format string "%s": %s',
221
 
                                    self.check_command, error)
 
224
                    logger.critical(u'Could not format string "%s":'
 
225
                                    u' %s', self.check_command, error)
222
226
                    return True # Try again later
223
227
            try:
 
228
                logger.debug(u"Starting checker %r for %s",
 
229
                             command, self.name)
224
230
                self.checker = subprocess.\
225
231
                               Popen(command,
226
 
                                     stdout=subprocess.PIPE,
227
232
                                     close_fds=True, shell=True,
228
233
                                     cwd="/")
229
 
                self.checker_callback_tag = gobject.\
230
 
                                            child_watch_add(self.checker.pid,
231
 
                                                            self.\
232
 
                                                            checker_callback)
 
234
                self.checker_callback_tag = gobject.child_watch_add\
 
235
                                            (self.checker.pid,
 
236
                                             self.checker_callback)
233
237
            except subprocess.OSError, error:
234
238
                logger.error(u"Failed to start subprocess: %s",
235
239
                             error)
256
260
 
257
261
 
258
262
def peer_certificate(session):
 
263
    "Return an OpenPGP data packet string for the peer's certificate"
259
264
    # If not an OpenPGP certificate...
260
265
    if gnutls.library.functions.gnutls_certificate_type_get\
261
266
            (session._c_object) \
272
277
 
273
278
 
274
279
def fingerprint(openpgp):
 
280
    "Convert an OpenPGP data string to a hexdigit fingerprint string"
275
281
    # New empty GnuTLS certificate
276
282
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
277
283
    gnutls.library.functions.gnutls_openpgp_crt_init\
503
509
            add_service()
504
510
            
505
511
        else:
506
 
            logger.error(u"No suitable service name found "
507
 
                         u"after %i retries, exiting.",
508
 
                         n_rename)
509
 
            main_loop.quit()
 
512
            logger.error(u"No suitable service name found after %i"
 
513
                         u" retries, exiting.", n_rename)
 
514
            killme(1)
510
515
    elif state == avahi.ENTRY_GROUP_FAILURE:
511
516
        logger.error(u"Error in group state changed %s",
512
517
                     unicode(error))
513
 
        main_loop.quit()
514
 
        return
 
518
        killme(1)
515
519
 
516
520
 
517
521
def if_nametoindex(interface):
533
537
        return interface_index
534
538
 
535
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
 
536
570
if __name__ == '__main__':
 
571
    exitstatus = 0
 
572
    main_loop_started = False
537
573
    parser = OptionParser()
538
574
    parser.add_option("-i", "--interface", type="string",
539
575
                      default=None, metavar="IF",
540
576
                      help="Bind to interface IF")
541
 
    parser.add_option("--cert", type="string", default="cert.pem",
542
 
                      metavar="FILE",
543
 
                      help="Public key certificate PEM file to use")
544
 
    parser.add_option("--key", type="string", default="key.pem",
545
 
                      metavar="FILE",
546
 
                      help="Private key PEM file to use")
547
 
    parser.add_option("--ca", type="string", default="ca.pem",
548
 
                      metavar="FILE",
549
 
                      help="Certificate Authority certificate PEM file to use")
550
 
    parser.add_option("--crl", type="string", default="crl.pem",
551
 
                      metavar="FILE",
552
 
                      help="Certificate Revokation List PEM file to use")
553
577
    parser.add_option("-p", "--port", type="int", default=None,
554
578
                      help="Port number to receive requests on")
555
579
    parser.add_option("--timeout", type="string", # Parsed later
580
604
        parser.error("option --interval: Unparseable time")
581
605
    
582
606
    # Parse config file
583
 
    defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
 
607
    defaults = { "checker": "fping -q -- %%(fqdn)s" }
584
608
    client_config = ConfigParser.SafeConfigParser(defaults)
585
609
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
586
610
    client_config.read("mandos-clients.conf")
596
620
    
597
621
    debug = options.debug
598
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
    
599
631
    clients = Set()
600
632
    def remove_from_clients(client):
601
633
        clients.remove(client)
602
634
        if not clients:
603
635
            logger.debug(u"No clients left, exiting")
604
 
            main_loop.quit()
 
636
            killme()
605
637
    
606
638
    clients.update(Set(Client(name=section, options=options,
607
639
                              stop_hook = remove_from_clients,
608
640
                              **(dict(client_config\
609
641
                                      .items(section))))
610
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
    
611
667
    for client in clients:
612
668
        client.start()
613
669
    
624
680
    
625
681
    # From the Avahi server example code
626
682
    server.connect_to_signal("StateChanged", server_state_changed)
627
 
    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)
628
688
    # End of Avahi example code
629
689
    
630
690
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
632
692
                         tcp_server.handle_request(*args[2:],
633
693
                                                   **kwargs) or True)
634
694
    try:
 
695
        main_loop_started = True
635
696
        main_loop.run()
636
697
    except KeyboardInterrupt:
637
 
        print
638
 
    
639
 
    # Cleanup here
640
 
 
641
 
    # From the Avahi server example code
642
 
    if not group is None:
643
 
        group.Free()
644
 
    # End of Avahi example code
645
 
    
646
 
    for client in clients:
647
 
        client.stop_hook = None
648
 
        client.stop()
 
698
        if debug:
 
699
            print
 
700
    
 
701
    sys.exit(exitstatus)