diff --git a/config b/config index 04f47c8..ae672af 100644 --- a/config +++ b/config @@ -6,14 +6,11 @@ # The current values are the default ones [server] -# CalDAV server hostname, empty for all hostnames -# if you want to bind to multiple interfaces, seperate them with a comma -# NOTE: IPv6 adresses are configured to only allow IPv6 connections -# hence for binding to all IPv4 and IPv6 interfaces: -# host = ::, 0.0.0.0 -host = -# CalDAV server port -port = 5232 +# CalDAV server hostnames separated by a comma +# IPv4: address:port +# IPv6: [address]:port +# IPv6 adresses are configured to only allow IPv6 connections +hosts = [::]:5232, 0.0.0.0:5232 # Daemon flag daemon = False # SSL flag, enable HTTPS protocol diff --git a/radicale.py b/radicale.py index 5e183e7..79d32a9 100755 --- a/radicale.py +++ b/radicale.py @@ -38,7 +38,8 @@ arguments. import os import sys import optparse -import threading, signal +import signal +import threading import radicale @@ -56,13 +57,9 @@ parser.add_option( "-f", "--foreground", action="store_false", dest="daemon", help="launch in foreground (opposite of --daemon)") parser.add_option( - "-H", "--host", - default=radicale.config.get("server", "host"), - help="set server hostname") -parser.add_option( - "-p", "--port", type="int", - default=radicale.config.getint("server", "port"), - help="set server port") + "-H", "--hosts", + default=radicale.config.get("server", "hosts"), + help="set server hostnames") parser.add_option( "-s", "--ssl", action="store_true", default=radicale.config.getboolean("server", "ssl"), @@ -98,42 +95,36 @@ if options.daemon: sys.exit() sys.stdout = sys.stderr = open(os.devnull, "w") -def exit (servers): - """Cleanly shutdown all servers. - - Might be called multiple times.""" - for s in servers: - s.shutdown() - -# Launch calendar server -server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer +# Launch calendar servers servers = [] -threads = [] +server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer -for host in (x.strip() for x in options.host.split(',')): +def exit(): + """Cleanly shutdown servers.""" + while servers: + servers.pop().shutdown() + +def serve_forever(server): + """Serve a server forever with no traceback on keyboard interrupts.""" try: - server = server_class( - (host, options.port), radicale.CalendarHTTPHandler) - servers.append(server) - - t = threading.Thread(target = server.serve_forever) - threads.append(t) - t.start() - except: - exit(servers) - raise + server.serve_forever() + except KeyboardInterrupt: + # No unwanted traceback + pass + finally: + exit() -# clean exit on SIGTERM -signal.signal(signal.SIGTERM, lambda *a: exit(servers)) +# Clean exit on SIGTERM +signal.signal(signal.SIGTERM, lambda *_: exit()) -try: - while threads: - threads[0].join(1) # try one second - if threading.active_count() <= len(threads): - # at least one thread died -- exit all - break -except KeyboardInterrupt: - # no unwanted traceback :) - pass -finally: - exit(servers) +for host in options.hosts.split(','): + address, port = host.strip().rsplit(':', 1) + address, port = address.strip('[] '), int(port) + servers.append(server_class((address, port), radicale.CalendarHTTPHandler)) + +for server in servers[:-1]: + # More servers to come, launch a new thread + threading.Thread(target=serve_forever, args=(server,)).start() + +# Last server, no more thread +serve_forever(servers[-1]) diff --git a/radicale/__init__.py b/radicale/__init__.py index 6e5865c..1c43420 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -85,18 +85,18 @@ class HTTPServer(server.HTTPServer): # Maybe a Pylint bug, ``__init__`` calls ``server.HTTPServer.__init__`` # pylint: disable=W0231 - def __init__(self, address, handler, bind_and_activate = True): + def __init__(self, address, handler, bind_and_activate=True): """Create server.""" - self.use_ipv6 = ':' in address[0] + ipv6 = ":" in address[0] - if self.use_ipv6: + if ipv6: self.address_family = socket.AF_INET6 - # call superclass, but do NOT bind and activate, as we might change socketopts - server.HTTPServer.__init__(self, address, handler, bind_and_activate = False) + # Do not bind and activate, as we might change socketopts + server.HTTPServer.__init__(self, address, handler, False) - if self.use_ipv6: - # only allow IPv6 connections to the IPv6 socket + if ipv6: + # Only allow IPv6 connections to the IPv6 socket self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) if bind_and_activate: @@ -111,7 +111,7 @@ class HTTPSServer(HTTPServer): """HTTPS server.""" PROTOCOL = "https" - def __init__(self, address, handler, bind_and_activate = True): + def __init__(self, address, handler, bind_and_activate=True): """Create server by wrapping HTTP socket in an SSL socket.""" # Fails with Python 2.5, import if needed # pylint: disable=F0401 @@ -120,7 +120,7 @@ class HTTPSServer(HTTPServer): HTTPServer.__init__(self, address, handler, False) self.socket = ssl.wrap_socket( - self.socket, # we can use this, it is not bound yet + self.socket, server_side=True, certfile=config.get("server", "certificate"), keyfile=config.get("server", "key"), diff --git a/radicale/config.py b/radicale/config.py index 63929ae..8fe1027 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -39,8 +39,7 @@ except ImportError: # Default configuration INITIAL_CONFIG = { "server": { - "host": "", - "port": "5232", + "hosts": "[::]:5232, 0.0.0.0:5232", "daemon": "False", "ssl": "False", "certificate": "/etc/apache2/ssl/server.crt",