diff --git a/NEWS b/NEWS index fa927ff1..7893995d 100644 --- a/NEWS +++ b/NEWS @@ -6,10 +6,17 @@ NEWS ------ -0.5 - *Not released yet* +0.6 - *Not released yet* ======================== +* IPv6 support + + +0.5 - Historical Artifacts +========================== + * Calendar depth +* iPhone support * MacOS and Windows support * HEAD requests management * htpasswd user from calendar path diff --git a/TODO b/TODO index f394b8ab..7ab5d0fd 100644 --- a/TODO +++ b/TODO @@ -6,17 +6,12 @@ TODO ------ -0.5 -=== - -* iCal and iPhone support - - 0.6 === * [IN PROGRESS] Group calendars * [IN PROGRESS] LDAP and databases auth support +* [IN PROGRESS] Smart, verbose and configurable logs * CalDAV rights * Read-only access for foreign users diff --git a/config b/config index 6380a7f3..b86a6323 100644 --- a/config +++ b/config @@ -6,10 +6,11 @@ # The current values are the default ones [server] -# CalDAV server hostname, empty for all hostnames -host = -# CalDAV server port -port = 5232 +# CalDAV server hostnames separated by a comma +# IPv4 syntax: address:port +# IPv6 syntax: [address]:port +# IPv6 adresses are configured to only allow IPv6 connections +hosts = 0.0.0.0:5232 # Daemon flag daemon = False # SSL flag, enable HTTPS protocol diff --git a/radicale.py b/radicale.py index 03f6a147..507dff70 100755 --- a/radicale.py +++ b/radicale.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- # # This file is part of Radicale Server - Calendar Server @@ -26,10 +26,9 @@ # pylint: disable-msg=W0406 """ -Radicale Server entry point. +Radicale CalDAV Server. -Launch the Radicale Server according to configuration and command-line -arguments. +Launch the server according to configuration and command-line options. """ @@ -38,15 +37,13 @@ arguments. import os import sys import optparse +import signal +import threading import radicale # Get command-line options -parser = optparse.OptionParser() -parser.add_option( - "-v", "--version", action="store_true", - default=False, - help="show version and exit") +parser = optparse.OptionParser(version=radicale.VERSION) parser.add_option( "-d", "--daemon", action="store_true", default=radicale.config.getboolean("server", "daemon"), @@ -55,13 +52,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 and ports") parser.add_option( "-s", "--ssl", action="store_true", default=radicale.config.getboolean("server", "ssl"), @@ -72,11 +65,11 @@ parser.add_option( parser.add_option( "-k", "--key", default=radicale.config.get("server", "key"), - help="private key file ") + help="set private key file") parser.add_option( "-c", "--certificate", default=radicale.config.get("server", "certificate"), - help="certificate file ") + help="set certificate file") options = parser.parse_args()[0] # Update Radicale configuration according to options @@ -86,19 +79,52 @@ for option in parser.option_list: value = getattr(options, key) radicale.config.set("server", key, value) -# Print version and exit if the option is given -if options.version: - print(radicale.VERSION) - sys.exit() - # Fork if Radicale is launched as daemon if options.daemon: if os.fork(): sys.exit() sys.stdout = sys.stderr = open(os.devnull, "w") -# Launch calendar server +# Create calendar servers +servers = [] server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer -server = server_class( - (options.host, options.port), radicale.CalendarHTTPHandler) -server.serve_forever() +shutdown_program = threading.Event() + +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)) + +# SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for shutdown +signal.signal(signal.SIGTERM, lambda *_: shutdown_program.set()) +signal.signal(signal.SIGINT, lambda *_: shutdown_program.set()) + +def serve_forever(server): + """Serve a server forever, cleanly shutdown when things go wrong.""" + try: + server.serve_forever() + finally: + shutdown_program.set() + +# Start the servers in a different loop to avoid possible race-conditions, when +# a server exists but another server is added to the list at the same time +for server in servers: + threading.Thread(target=serve_forever, args=(server,)).start() + +# Main loop: wait until all servers are exited +try: + # We must do the busy-waiting here, as all ``.join()`` calls completly + # block the thread, such that signals are not received + while True: + # The number is irrelevant, it only needs to be greater than 0.05 due + # to python implementing its own busy-waiting logic + shutdown_program.wait(5.0) + if shutdown_program.is_set(): + break +finally: + # Ignore signals, so that they cannot interfere + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + + for server in servers: + server.shutdown() diff --git a/radicale/__init__.py b/radicale/__init__.py index 18b54266..1627a530 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -88,10 +88,26 @@ class HTTPServer(server.HTTPServer): # Maybe a Pylint bug, ``__init__`` calls ``server.HTTPServer.__init__`` # pylint: disable=W0231 - def __init__(self, address, handler): + def __init__(self, address, handler, bind_and_activate=True): """Create server.""" log.log(10, "Create HTTP server.") server.HTTPServer.__init__(self, address, handler) + ipv6 = ":" in address[0] + + if ipv6: + self.address_family = socket.AF_INET6 + + # Do not bind and activate, as we might change socketopts + server.HTTPServer.__init__(self, address, handler, False) + + if ipv6: + # Only allow IPv6 connections to the IPv6 socket + self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) + + if bind_and_activate: + self.server_bind() + self.server_activate() + self.acl = acl.load() # pylint: enable=W0231 @@ -100,7 +116,7 @@ class HTTPSServer(HTTPServer): """HTTPS server.""" PROTOCOL = "https" - def __init__(self, address, handler): + def __init__(self, address, handler, bind_and_activate=True): """Create server by wrapping HTTP socket in an SSL socket.""" log.log(10, "Create server by wrapping HTTP socket in an SSL socket.") # Fails with Python 2.5, import if needed @@ -108,15 +124,17 @@ class HTTPSServer(HTTPServer): import ssl # pylint: enable=F0401 - HTTPServer.__init__(self, address, handler) + HTTPServer.__init__(self, address, handler, False) self.socket = ssl.wrap_socket( - socket.socket(self.address_family, self.socket_type), + self.socket, server_side=True, certfile=config.get("server", "certificate"), keyfile=config.get("server", "key"), ssl_version=ssl.PROTOCOL_SSLv23) - self.server_bind() - self.server_activate() + + if bind_and_activate: + self.server_bind() + self.server_activate() class CalendarHTTPHandler(server.BaseHTTPRequestHandler): diff --git a/radicale/config.py b/radicale/config.py index c6bbee45..786b1aed 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -39,8 +39,7 @@ except ImportError: # Default configuration INITIAL_CONFIG = { "server": { - "host": "", - "port": "5232", + "hosts": "0.0.0.0:5232", "daemon": "False", "ssl": "False", "certificate": "/etc/apache2/ssl/server.crt", diff --git a/setup.py b/setup.py index 2d9b09cf..fe745165 100755 --- a/setup.py +++ b/setup.py @@ -27,8 +27,8 @@ it requires few software dependances and is pre-configured to work out-of-the-box. The Radicale Project runs on most of the UNIX-like platforms (Linux, BSD, -MacOS X) and Windows. It is known to work with Evolution 2.30+, Lightning 0.9+ -and Sunbird 0.9+. It is free and open-source software, released under GPL +MacOS X) and Windows. It is known to work with Evolution, Lightning, iPhone +and Android clients. It is free and open-source software, released under GPL version 3. For further information, please visit the `Radicale Website @@ -91,4 +91,5 @@ setup( "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", + "Programming Language :: Python :: 3.2", "Topic :: Office/Business :: Groupware"])