/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: Björn Påhlsson
  • Date: 2008-07-20 02:52:20 UTC
  • Revision ID: belorn@braxen-20080720025220-r5u0388uy9iu23h6
Added following support:
Pluginbased client handler
rewritten Mandos client
       Avahi instead of udp server discovery
       openpgp encrypted key support
Passprompt stand alone application for direct console input
Added logging for Mandos server

Show diffs side-by-side

added added

removed removed

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