/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-08-04 21:25:55 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080804212555-rm7xxjze65f8avy3
* server.py: Cosmetic changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
96
96
 
97
97
 
98
98
class AvahiService(object):
99
 
    """
 
99
    """An Avahi (Zeroconf) service.
 
100
    Attributes:
100
101
    interface: integer; avahi.IF_UNSPEC or an interface index.
101
102
               Used to optionally bind to the specified interface.
102
 
    name = string; Example: "Mandos"
103
 
    type = string; Example: "_mandos._tcp".
104
 
                   See <http://www.dns-sd.org/ServiceTypes.html>
105
 
    port = integer; what port to announce
106
 
    TXT = list of strings; TXT record for the service
107
 
    domain = string; Domain to publish on, default to .local if empty.
108
 
    host = string; Host to publish records for, default to localhost
109
 
                   if empty.
110
 
    max_renames = integer; maximum number of renames
111
 
    rename_count = integer; counter so we only rename after collisions
112
 
                   a sensible number of times
 
103
    name: string; Example: 'Mandos'
 
104
    type: string; Example: '_mandos._tcp'.
 
105
                  See <http://www.dns-sd.org/ServiceTypes.html>
 
106
    port: integer; what port to announce
 
107
    TXT: list of strings; TXT record for the service
 
108
    domain: string; Domain to publish on, default to .local if empty.
 
109
    host: string; Host to publish records for, default is localhost
 
110
    max_renames: integer; maximum number of renames
 
111
    rename_count: integer; counter so we only rename after collisions
 
112
                  a sensible number of times
113
113
    """
114
114
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
115
115
                 type = None, port = None, TXT = None, domain = "",
116
116
                 host = "", max_renames = 12):
117
 
        """An Avahi (Zeroconf) service. """
118
117
        self.interface = interface
119
118
        self.name = name
120
119
        self.type = type
133
132
                            u" retries, exiting.", rename_count)
134
133
            raise AvahiServiceError("Too many renames")
135
134
        name = server.GetAlternativeServiceName(name)
136
 
        logger.notice(u"Changing name to %r ...", name)
 
135
        logger.error(u"Changing name to %r ...", name)
137
136
        self.remove()
138
137
        self.add()
139
138
        self.rename_count += 1
221
220
    interval = property(lambda self: self._interval,
222
221
                        _set_interval)
223
222
    del _set_interval
224
 
    def __init__(self, name=None, stop_hook=None, fingerprint=None,
225
 
                 secret=None, secfile=None, fqdn=None, timeout=None,
226
 
                 interval=-1, checker=None):
227
 
        """Note: the 'checker' argument sets the 'checker_command'
228
 
        attribute and not the 'checker' attribute.."""
 
223
    def __init__(self, name = None, stop_hook=None, config={}):
 
224
        """Note: the 'checker' key in 'config' sets the
 
225
        'checker_command' attribute and *not* the 'checker'
 
226
        attribute."""
229
227
        self.name = name
230
228
        logger.debug(u"Creating client %r", self.name)
231
 
        # Uppercase and remove spaces from fingerprint
232
 
        # for later comparison purposes with return value of
233
 
        # the fingerprint() function
234
 
        self.fingerprint = fingerprint.upper().replace(u" ", u"")
 
229
        # Uppercase and remove spaces from fingerprint for later
 
230
        # comparison purposes with return value from the fingerprint()
 
231
        # function
 
232
        self.fingerprint = config["fingerprint"].upper()\
 
233
                           .replace(u" ", u"")
235
234
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
236
 
        if secret:
237
 
            self.secret = secret.decode(u"base64")
238
 
        elif secfile:
239
 
            sf = open(secfile)
 
235
        if "secret" in config:
 
236
            self.secret = config["secret"].decode(u"base64")
 
237
        elif "secfile" in config:
 
238
            sf = open(config["secfile"])
240
239
            self.secret = sf.read()
241
240
            sf.close()
242
241
        else:
243
242
            raise TypeError(u"No secret or secfile for client %s"
244
243
                            % self.name)
245
 
        self.fqdn = fqdn
 
244
        self.fqdn = config.get("fqdn", "")
246
245
        self.created = datetime.datetime.now()
247
246
        self.last_checked_ok = None
248
 
        self.timeout = string_to_delta(timeout)
249
 
        self.interval = string_to_delta(interval)
 
247
        self.timeout = string_to_delta(config["timeout"])
 
248
        self.interval = string_to_delta(config["interval"])
250
249
        self.stop_hook = stop_hook
251
250
        self.checker = None
252
251
        self.checker_initiator_tag = None
253
252
        self.stop_initiator_tag = None
254
253
        self.checker_callback_tag = None
255
 
        self.check_command = checker
 
254
        self.check_command = config["checker"]
256
255
    def start(self):
257
256
        """Start this client's checker and timeout hooks"""
258
257
        # Schedule a new checker to be started an 'interval' from now,
272
271
        but not currently used."""
273
272
        # If this client doesn't have a secret, it is already stopped.
274
273
        if self.secret:
275
 
            logger.debug(u"Stopping client %s", self.name)
 
274
            logger.info(u"Stopping client %s", self.name)
276
275
            self.secret = None
277
276
        else:
278
277
            return False
297
296
        self.checker = None
298
297
        if os.WIFEXITED(condition) \
299
298
               and (os.WEXITSTATUS(condition) == 0):
300
 
            logger.debug(u"Checker for %(name)s succeeded",
301
 
                         vars(self))
 
299
            logger.info(u"Checker for %(name)s succeeded",
 
300
                        vars(self))
302
301
            self.last_checked_ok = now
303
302
            gobject.source_remove(self.stop_initiator_tag)
304
303
            self.stop_initiator_tag = gobject.timeout_add\
308
307
            logger.warning(u"Checker for %(name)s crashed?",
309
308
                           vars(self))
310
309
        else:
311
 
            logger.debug(u"Checker for %(name)s failed",
312
 
                         vars(self))
 
310
            logger.info(u"Checker for %(name)s failed",
 
311
                        vars(self))
313
312
    def start_checker(self):
314
313
        """Start a new checker subprocess if one is not running.
315
314
        If a checker already exists, leave it running and do
338
337
                                 u' %s', self.check_command, error)
339
338
                    return True # Try again later
340
339
            try:
341
 
                logger.debug(u"Starting checker %r for %s",
342
 
                             command, self.name)
 
340
                logger.info(u"Starting checker %r for %s",
 
341
                            command, self.name)
343
342
                self.checker = subprocess.Popen(command,
344
343
                                                close_fds=True,
345
344
                                                shell=True, cwd="/")
396
395
 
397
396
def fingerprint(openpgp):
398
397
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
399
 
    # New empty GnuTLS certificate
400
 
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
401
 
    gnutls.library.functions.gnutls_openpgp_crt_init\
402
 
        (ctypes.byref(crt))
403
398
    # New GnuTLS "datum" with the OpenPGP public key
404
399
    datum = gnutls.library.types.gnutls_datum_t\
405
400
        (ctypes.cast(ctypes.c_char_p(openpgp),
406
401
                     ctypes.POINTER(ctypes.c_ubyte)),
407
402
         ctypes.c_uint(len(openpgp)))
 
403
    # New empty GnuTLS certificate
 
404
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
405
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
406
        (ctypes.byref(crt))
408
407
    # Import the OpenPGP public key into the certificate
409
 
    ret = gnutls.library.functions.gnutls_openpgp_crt_import\
410
 
        (crt,
411
 
         ctypes.byref(datum),
412
 
         gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
408
    gnutls.library.functions.gnutls_openpgp_crt_import\
 
409
                    (crt, ctypes.byref(datum),
 
410
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
413
411
    # New buffer for the fingerprint
414
412
    buffer = ctypes.create_string_buffer(20)
415
413
    buffer_length = ctypes.c_size_t()
431
429
    Note: This will run in its own forked process."""
432
430
    
433
431
    def handle(self):
434
 
        logger.debug(u"TCP connection from: %s",
 
432
        logger.info(u"TCP connection from: %s",
435
433
                     unicode(self.client_address))
436
434
        session = gnutls.connection.ClientSession\
437
435
                  (self.request, gnutls.connection.X509Credentials())
 
436
        
 
437
        line = self.request.makefile().readline()
 
438
        logger.debug(u"Protocol version: %r", line)
 
439
        try:
 
440
            if int(line.strip().split()[0]) > 1:
 
441
                raise RuntimeError
 
442
        except (ValueError, IndexError, RuntimeError), error:
 
443
            logger.error(u"Unknown protocol version: %s", error)
 
444
            return
 
445
        
438
446
        # Note: gnutls.connection.X509Credentials is really a generic
439
447
        # GnuTLS certificate credentials object so long as no X.509
440
448
        # keys are added to it.  Therefore, we can use it here despite
453
461
        try:
454
462
            session.handshake()
455
463
        except gnutls.errors.GNUTLSError, error:
456
 
            logger.debug(u"Handshake failed: %s", error)
 
464
            logger.warning(u"Handshake failed: %s", error)
457
465
            # Do not run session.bye() here: the session is not
458
466
            # established.  Just abandon the request.
459
467
            return
460
468
        try:
461
469
            fpr = fingerprint(peer_certificate(session))
462
470
        except (TypeError, gnutls.errors.GNUTLSError), error:
463
 
            logger.debug(u"Bad certificate: %s", error)
 
471
            logger.warning(u"Bad certificate: %s", error)
464
472
            session.bye()
465
473
            return
466
474
        logger.debug(u"Fingerprint: %s", fpr)
470
478
                client = c
471
479
                break
472
480
        if not client:
473
 
            logger.debug(u"Client not found for fingerprint: %s", fpr)
 
481
            logger.warning(u"Client not found for fingerprint: %s",
 
482
                           fpr)
474
483
            session.bye()
475
484
            return
476
485
        # Have to check if client.still_valid(), since it is possible
477
486
        # that the client timed out while establishing the GnuTLS
478
487
        # session.
479
488
        if not client.still_valid():
480
 
            logger.debug(u"Client %(name)s is invalid", vars(client))
 
489
            logger.warning(u"Client %(name)s is invalid",
 
490
                           vars(client))
481
491
            session.bye()
482
492
            return
483
493
        sent_size = 0
509
519
        """This overrides the normal server_bind() function
510
520
        to bind to an interface if one was specified, and also NOT to
511
521
        bind to an address or port if they were not specified."""
512
 
        if self.settings["interface"] != avahi.IF_UNSPEC:
 
522
        if self.settings["interface"]:
513
523
            # 25 is from /usr/include/asm-i486/socket.h
514
524
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
515
525
            try:
518
528
                                       self.settings["interface"])
519
529
            except socket.error, error:
520
530
                if error[0] == errno.EPERM:
521
 
                    logger.warning(u"No permission to"
522
 
                                   u" bind to interface %s",
523
 
                                   self.settings["interface"])
 
531
                    logger.error(u"No permission to"
 
532
                                 u" bind to interface %s",
 
533
                                 self.settings["interface"])
524
534
                else:
525
535
                    raise error
526
536
        # Only bind(2) the socket if we really need to.
572
582
def server_state_changed(state):
573
583
    """Derived from the Avahi example code"""
574
584
    if state == avahi.SERVER_COLLISION:
575
 
        logger.warning(u"Server name collision")
 
585
        logger.error(u"Server name collision")
576
586
        service.remove()
577
587
    elif state == avahi.SERVER_RUNNING:
578
588
        service.add()
592
602
                        unicode(error))
593
603
        raise AvahiGroupError("State changed: %s", str(error))
594
604
 
595
 
def if_nametoindex(interface, _func=[None]):
 
605
def if_nametoindex(interface):
596
606
    """Call the C function if_nametoindex(), or equivalent"""
597
 
    if _func[0] is not None:
598
 
        return _func[0](interface)
 
607
    global if_nametoindex
599
608
    try:
600
609
        if "ctypes.util" not in sys.modules:
601
610
            import ctypes.util
602
 
        while True:
603
 
            try:
604
 
                libc = ctypes.cdll.LoadLibrary\
605
 
                       (ctypes.util.find_library("c"))
606
 
                func[0] = libc.if_nametoindex
607
 
                return _func[0](interface)
608
 
            except IOError, e:
609
 
                if e != errno.EINTR:
610
 
                    raise
 
611
        if_nametoindex = ctypes.cdll.LoadLibrary\
 
612
            (ctypes.util.find_library("c")).if_nametoindex
611
613
    except (OSError, AttributeError):
612
614
        if "struct" not in sys.modules:
613
615
            import struct
614
616
        if "fcntl" not in sys.modules:
615
617
            import fcntl
616
 
        def the_hard_way(interface):
 
618
        def if_nametoindex(interface):
617
619
            "Get an interface index the hard way, i.e. using fcntl()"
618
620
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
619
621
            s = socket.socket()
622
624
            s.close()
623
625
            interface_index = struct.unpack("I", ifreq[16:20])[0]
624
626
            return interface_index
625
 
        _func[0] = the_hard_way
626
 
        return _func[0](interface)
 
627
    return if_nametoindex(interface)
627
628
 
628
629
 
629
630
def daemon(nochdir, noclose):
699
700
    server_settings["debug"] = server_config.getboolean\
700
701
                               (server_section, "debug")
701
702
    del server_config
702
 
    if not server_settings["interface"]:
703
 
        server_settings["interface"] = avahi.IF_UNSPEC
704
703
    
705
704
    # Override the settings from the config file with command line
706
705
    # options, if set.
724
723
    global service
725
724
    service = AvahiService(name = server_settings["servicename"],
726
725
                           type = "_mandos._tcp", );
 
726
    if server_settings["interface"]:
 
727
        service.interface = if_nametoindex(server_settings["interface"])
727
728
    
728
729
    global main_loop
729
730
    global bus
751
752
    def remove_from_clients(client):
752
753
        clients.remove(client)
753
754
        if not clients:
754
 
            logger.debug(u"No clients left, exiting")
 
755
            logger.critical(u"No clients left, exiting")
755
756
            sys.exit()
756
757
    
757
 
    clients.update(Set(Client(name=section,
 
758
    clients.update(Set(Client(name = section,
758
759
                              stop_hook = remove_from_clients,
759
 
                              **(dict(client_config\
760
 
                                      .items(section))))
 
760
                              config
 
761
                              = dict(client_config.items(section)))
761
762
                       for section in client_config.sections()))
762
763
    
763
764
    if not debug:
794
795
                                clients=clients)
795
796
    # Find out what port we got
796
797
    service.port = tcp_server.socket.getsockname()[1]
797
 
    logger.debug(u"Now listening on port %d", service.port)
 
798
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
 
799
                u" scope_id %d" % tcp_server.socket.getsockname())
798
800
    
799
 
    if not server_settings["interface"]:
800
 
        service.interface = if_nametoindex\
801
 
                            (server_settings["interface"])
 
801
    #service.interface = tcp_server.socket.getsockname()[3]
802
802
    
803
803
    try:
804
804
        # From the Avahi example code