/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 mandos

  • Committer: Teddy Hogeborn
  • Date: 2008-09-13 15:36:18 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080913153618-atp386q2bqj0ku99
* Makefile (install-client-nokey): Do "&&" instead of ";" to catch
                                   errors.

* README: Kill the straight quotes.  Add copyright notice.

* overview.xml: Improved wording.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
 
14
# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
15
15
16
16
# This program is free software: you can redistribute it and/or modify
17
17
# it under the terms of the GNU General Public License as published by
30
30
# Contact the authors at <mandos@fukt.bsnet.se>.
31
31
32
32
 
33
 
from __future__ import division, with_statement, absolute_import
 
33
from __future__ import division
34
34
 
35
35
import SocketServer
36
36
import socket
 
37
import select
37
38
from optparse import OptionParser
38
39
import datetime
39
40
import errno
55
56
import logging
56
57
import logging.handlers
57
58
import pwd
58
 
from contextlib import closing
59
59
 
60
60
import dbus
61
61
import gobject
62
62
import avahi
63
63
from dbus.mainloop.glib import DBusGMainLoop
64
64
import ctypes
65
 
import ctypes.util
66
65
 
67
 
version = "1.0.2"
 
66
version = "1.0"
68
67
 
69
68
logger = logging.Logger('mandos')
70
69
syslogger = logging.handlers.SysLogHandler\
82
81
class AvahiError(Exception):
83
82
    def __init__(self, value):
84
83
        self.value = value
85
 
        super(AvahiError, self).__init__()
86
84
    def __str__(self):
87
85
        return repr(self.value)
88
86
 
110
108
                  a sensible number of times
111
109
    """
112
110
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
113
 
                 servicetype = None, port = None, TXT = None, domain = "",
 
111
                 type = None, port = None, TXT = None, domain = "",
114
112
                 host = "", max_renames = 32768):
115
113
        self.interface = interface
116
114
        self.name = name
117
 
        self.type = servicetype
 
115
        self.type = type
118
116
        self.port = port
119
117
        if TXT is None:
120
118
            self.TXT = []
129
127
        if self.rename_count >= self.max_renames:
130
128
            logger.critical(u"No suitable Zeroconf service name found"
131
129
                            u" after %i retries, exiting.",
132
 
                            self.rename_count)
 
130
                            rename_count)
133
131
            raise AvahiServiceError("Too many renames")
134
132
        self.name = server.GetAlternativeServiceName(self.name)
135
133
        logger.info(u"Changing Zeroconf service name to %r ...",
224
222
    interval = property(lambda self: self._interval,
225
223
                        _set_interval)
226
224
    del _set_interval
227
 
    def __init__(self, name = None, stop_hook=None, config=None):
 
225
    def __init__(self, name = None, stop_hook=None, config={}):
228
226
        """Note: the 'checker' key in 'config' sets the
229
227
        'checker_command' attribute and *not* the 'checker'
230
228
        attribute."""
231
 
        if config is None:
232
 
            config = {}
233
229
        self.name = name
234
230
        logger.debug(u"Creating client %r", self.name)
235
231
        # Uppercase and remove spaces from fingerprint for later
241
237
        if "secret" in config:
242
238
            self.secret = config["secret"].decode(u"base64")
243
239
        elif "secfile" in config:
244
 
            with closing(open(os.path.expanduser
245
 
                              (os.path.expandvars
246
 
                               (config["secfile"])))) \
247
 
                               as secfile:
248
 
                self.secret = secfile.read()
 
240
            sf = open(config["secfile"])
 
241
            self.secret = sf.read()
 
242
            sf.close()
249
243
        else:
250
244
            raise TypeError(u"No secret or secfile for client %s"
251
245
                            % self.name)
299
293
        self.stop()
300
294
    def checker_callback(self, pid, condition):
301
295
        """The checker has completed, so take appropriate actions."""
 
296
        now = datetime.datetime.now()
302
297
        self.checker_callback_tag = None
303
298
        self.checker = None
304
299
        if os.WIFEXITED(condition) \
305
300
               and (os.WEXITSTATUS(condition) == 0):
306
301
            logger.info(u"Checker for %(name)s succeeded",
307
302
                        vars(self))
308
 
            self.bump_timeout()
 
303
            self.last_checked_ok = now
 
304
            gobject.source_remove(self.stop_initiator_tag)
 
305
            self.stop_initiator_tag = gobject.timeout_add\
 
306
                                      (self._timeout_milliseconds,
 
307
                                       self.stop)
309
308
        elif not os.WIFEXITED(condition):
310
309
            logger.warning(u"Checker for %(name)s crashed?",
311
310
                           vars(self))
312
311
        else:
313
312
            logger.info(u"Checker for %(name)s failed",
314
313
                        vars(self))
315
 
    def bump_timeout(self):
316
 
        """Bump up the timeout for this client.
317
 
        This should only be called when the client has been seen,
318
 
        alive and well.
319
 
        """
320
 
        self.last_checked_ok = datetime.datetime.now()
321
 
        gobject.source_remove(self.stop_initiator_tag)
322
 
        self.stop_initiator_tag = gobject.timeout_add\
323
 
            (self._timeout_milliseconds, self.stop)
324
314
    def start_checker(self):
325
315
        """Start a new checker subprocess if one is not running.
326
316
        If a checker already exists, leave it running and do
425
415
                    (crt, ctypes.byref(datum),
426
416
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
427
417
    # Verify the self signature in the key
428
 
    crtverify = ctypes.c_uint()
 
418
    crtverify = ctypes.c_uint();
429
419
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
430
420
        (crt, 0, ctypes.byref(crtverify))
431
421
    if crtverify.value != 0:
432
422
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
433
423
        raise gnutls.errors.CertificateSecurityError("Verify failed")
434
424
    # New buffer for the fingerprint
435
 
    buf = ctypes.create_string_buffer(20)
436
 
    buf_len = ctypes.c_size_t()
 
425
    buffer = ctypes.create_string_buffer(20)
 
426
    buffer_length = ctypes.c_size_t()
437
427
    # Get the fingerprint from the certificate into the buffer
438
428
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
439
 
        (crt, ctypes.byref(buf), ctypes.byref(buf_len))
 
429
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
440
430
    # Deinit the certificate
441
431
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
442
432
    # Convert the buffer to a Python bytestring
443
 
    fpr = ctypes.string_at(buf, buf_len.value)
 
433
    fpr = ctypes.string_at(buffer, buffer_length.value)
444
434
    # Convert the bytestring to hexadecimal notation
445
435
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
446
436
    return hex_fpr
447
437
 
448
438
 
449
 
class TCP_handler(SocketServer.BaseRequestHandler, object):
 
439
class tcp_handler(SocketServer.BaseRequestHandler, object):
450
440
    """A TCP request handler class.
451
441
    Instantiated by IPv6_TCPServer for each request to handle it.
452
442
    Note: This will run in its own forked process."""
453
443
    
454
444
    def handle(self):
455
445
        logger.info(u"TCP connection from: %s",
456
 
                    unicode(self.client_address))
 
446
                     unicode(self.client_address))
457
447
        session = gnutls.connection.ClientSession\
458
448
                  (self.request, gnutls.connection.X509Credentials())
459
449
        
474
464
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
475
465
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
476
466
        #                "+DHE-DSS"))
477
 
        # Use a fallback default, since this MUST be set.
478
 
        priority = self.server.settings.get("priority", "NORMAL")
 
467
        priority = "NORMAL"             # Fallback default, since this
 
468
                                        # MUST be set.
 
469
        if self.server.settings["priority"]:
 
470
            priority = self.server.settings["priority"]
479
471
        gnutls.library.functions.gnutls_priority_set_direct\
480
 
            (session._c_object, priority, None)
 
472
            (session._c_object, priority, None);
481
473
        
482
474
        try:
483
475
            session.handshake()
511
503
                           vars(client))
512
504
            session.bye()
513
505
            return
514
 
        ## This won't work here, since we're in a fork.
515
 
        # client.bump_timeout()
516
506
        sent_size = 0
517
507
        while sent_size < len(client.secret):
518
508
            sent = session.send(client.secret[sent_size:])
523
513
        session.bye()
524
514
 
525
515
 
526
 
class IPv6_TCPServer(SocketServer.ForkingMixIn,
527
 
                     SocketServer.TCPServer, object):
 
516
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
528
517
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
529
518
    Attributes:
530
519
        settings:       Server settings
540
529
            self.clients = kwargs["clients"]
541
530
            del kwargs["clients"]
542
531
        self.enabled = False
543
 
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
 
532
        return super(type(self), self).__init__(*args, **kwargs)
544
533
    def server_bind(self):
545
534
        """This overrides the normal server_bind() function
546
535
        to bind to an interface if one was specified, and also NOT to
575
564
#                                            if_nametoindex
576
565
#                                            (self.settings
577
566
#                                             ["interface"]))
578
 
            return super(IPv6_TCPServer, self).server_bind()
 
567
            return super(type(self), self).server_bind()
579
568
    def server_activate(self):
580
569
        if self.enabled:
581
 
            return super(IPv6_TCPServer, self).server_activate()
 
570
            return super(type(self), self).server_activate()
582
571
    def enable(self):
583
572
        self.enabled = True
584
573
 
602
591
    timevalue = datetime.timedelta(0)
603
592
    for s in interval.split():
604
593
        try:
605
 
            suffix = unicode(s[-1])
606
 
            value = int(s[:-1])
 
594
            suffix=unicode(s[-1])
 
595
            value=int(s[:-1])
607
596
            if suffix == u"d":
608
597
                delta = datetime.timedelta(value)
609
598
            elif suffix == u"s":
649
638
    """Call the C function if_nametoindex(), or equivalent"""
650
639
    global if_nametoindex
651
640
    try:
 
641
        if "ctypes.util" not in sys.modules:
 
642
            import ctypes.util
652
643
        if_nametoindex = ctypes.cdll.LoadLibrary\
653
644
            (ctypes.util.find_library("c")).if_nametoindex
654
645
    except (OSError, AttributeError):
659
650
        def if_nametoindex(interface):
660
651
            "Get an interface index the hard way, i.e. using fcntl()"
661
652
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
662
 
            with closing(socket.socket()) as s:
663
 
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
664
 
                                    struct.pack("16s16x", interface))
 
653
            s = socket.socket()
 
654
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
655
                                struct.pack("16s16x", interface))
 
656
            s.close()
665
657
            interface_index = struct.unpack("I", ifreq[16:20])[0]
666
658
            return interface_index
667
659
    return if_nametoindex(interface)
691
683
 
692
684
 
693
685
def main():
 
686
    global main_loop_started
 
687
    main_loop_started = False
 
688
    
694
689
    parser = OptionParser(version = "%%prog %s" % version)
695
690
    parser.add_option("-i", "--interface", type="string",
696
691
                      metavar="IF", help="Bind to interface IF")
711
706
                      default="/etc/mandos", metavar="DIR",
712
707
                      help="Directory to search for configuration"
713
708
                      " files")
714
 
    options = parser.parse_args()[0]
 
709
    (options, args) = parser.parse_args()
715
710
    
716
711
    if options.check:
717
712
        import doctest
774
769
    clients = Set()
775
770
    tcp_server = IPv6_TCPServer((server_settings["address"],
776
771
                                 server_settings["port"]),
777
 
                                TCP_handler,
 
772
                                tcp_handler,
778
773
                                settings=server_settings,
779
774
                                clients=clients)
780
775
    pidfilename = "/var/run/mandos.pid"
808
803
    
809
804
    global service
810
805
    service = AvahiService(name = server_settings["servicename"],
811
 
                           servicetype = "_mandos._tcp", )
 
806
                           type = "_mandos._tcp", );
812
807
    if server_settings["interface"]:
813
808
        service.interface = if_nametoindex\
814
809
                            (server_settings["interface"])
857
852
        pidfile.write(str(pid) + "\n")
858
853
        pidfile.close()
859
854
        del pidfile
860
 
    except IOError:
 
855
    except IOError, err:
861
856
        logger.error(u"Could not write to file %r with PID %d",
862
857
                     pidfilename, pid)
863
858
    except NameError:
915
910
                             (*args[2:], **kwargs) or True)
916
911
        
917
912
        logger.debug(u"Starting main loop")
 
913
        main_loop_started = True
918
914
        main_loop.run()
919
915
    except AvahiError, error:
920
916
        logger.critical(u"AvahiError: %s" + unicode(error))