Browse Source

Merge branch 'master' of git://gitorious.org/radicale/radicale

Conflicts:
	radicale/__init__.py
Corentin Le Bail 15 years ago
parent
commit
f8137315c0
7 changed files with 95 additions and 48 deletions
  1. 8 1
      NEWS
  2. 1 6
      TODO
  3. 5 4
      config
  4. 53 27
      radicale.py
  5. 24 6
      radicale/__init__.py
  6. 1 2
      radicale/config.py
  7. 3 2
      setup.py

+ 8 - 1
NEWS

@@ -6,10 +6,17 @@
  NEWS
  NEWS
 ------
 ------
 
 
-0.5 - *Not released yet*
+0.6 - *Not released yet*
 ========================
 ========================
 
 
+* IPv6 support
+
+
+0.5 - Historical Artifacts
+==========================
+
 * Calendar depth
 * Calendar depth
+* iPhone support
 * MacOS and Windows support
 * MacOS and Windows support
 * HEAD requests management
 * HEAD requests management
 * htpasswd user from calendar path
 * htpasswd user from calendar path

+ 1 - 6
TODO

@@ -6,17 +6,12 @@
  TODO
  TODO
 ------
 ------
 
 
-0.5
-===
-
-* iCal and iPhone support
-
-
 0.6
 0.6
 ===
 ===
 
 
 * [IN PROGRESS] Group calendars
 * [IN PROGRESS] Group calendars
 * [IN PROGRESS] LDAP and databases auth support
 * [IN PROGRESS] LDAP and databases auth support
+* [IN PROGRESS] Smart, verbose and configurable logs
 * CalDAV rights
 * CalDAV rights
 * Read-only access for foreign users
 * Read-only access for foreign users
 
 

+ 5 - 4
config

@@ -6,10 +6,11 @@
 # The current values are the default ones
 # The current values are the default ones
 
 
 [server]
 [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 flag
 daemon = False
 daemon = False
 # SSL flag, enable HTTPS protocol
 # SSL flag, enable HTTPS protocol

+ 53 - 27
radicale.py

@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 #
 #
 # This file is part of Radicale Server - Calendar Server
 # This file is part of Radicale Server - Calendar Server
@@ -26,10 +26,9 @@
 # pylint: disable-msg=W0406
 # 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 os
 import sys
 import sys
 import optparse
 import optparse
+import signal
+import threading
 
 
 import radicale
 import radicale
 
 
 # Get command-line options
 # 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(
 parser.add_option(
     "-d", "--daemon", action="store_true",
     "-d", "--daemon", action="store_true",
     default=radicale.config.getboolean("server", "daemon"),
     default=radicale.config.getboolean("server", "daemon"),
@@ -55,13 +52,9 @@ parser.add_option(
     "-f", "--foreground", action="store_false", dest="daemon",
     "-f", "--foreground", action="store_false", dest="daemon",
     help="launch in foreground (opposite of --daemon)")
     help="launch in foreground (opposite of --daemon)")
 parser.add_option(
 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(
 parser.add_option(
     "-s", "--ssl", action="store_true",
     "-s", "--ssl", action="store_true",
     default=radicale.config.getboolean("server", "ssl"),
     default=radicale.config.getboolean("server", "ssl"),
@@ -72,11 +65,11 @@ parser.add_option(
 parser.add_option(
 parser.add_option(
     "-k", "--key",
     "-k", "--key",
     default=radicale.config.get("server", "key"),
     default=radicale.config.get("server", "key"),
-    help="private key file ")
+    help="set private key file")
 parser.add_option(
 parser.add_option(
     "-c", "--certificate",
     "-c", "--certificate",
     default=radicale.config.get("server", "certificate"),
     default=radicale.config.get("server", "certificate"),
-    help="certificate file ")
+    help="set certificate file")
 options = parser.parse_args()[0]
 options = parser.parse_args()[0]
 
 
 # Update Radicale configuration according to options
 # Update Radicale configuration according to options
@@ -86,19 +79,52 @@ for option in parser.option_list:
         value = getattr(options, key)
         value = getattr(options, key)
         radicale.config.set("server", key, value)
         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
 # Fork if Radicale is launched as daemon
 if options.daemon:
 if options.daemon:
     if os.fork():
     if os.fork():
         sys.exit()
         sys.exit()
     sys.stdout = sys.stderr = open(os.devnull, "w")
     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_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()

+ 24 - 6
radicale/__init__.py

@@ -88,10 +88,26 @@ class HTTPServer(server.HTTPServer):
 
 
     # Maybe a Pylint bug, ``__init__`` calls ``server.HTTPServer.__init__``
     # Maybe a Pylint bug, ``__init__`` calls ``server.HTTPServer.__init__``
     # pylint: disable=W0231
     # pylint: disable=W0231
-    def __init__(self, address, handler):
+    def __init__(self, address, handler, bind_and_activate=True):
         """Create server."""
         """Create server."""
         log.log(10, "Create HTTP server.")
         log.log(10, "Create HTTP server.")
         server.HTTPServer.__init__(self, address, handler)
         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()
         self.acl = acl.load()
     # pylint: enable=W0231
     # pylint: enable=W0231
 
 
@@ -100,7 +116,7 @@ class HTTPSServer(HTTPServer):
     """HTTPS server."""
     """HTTPS server."""
     PROTOCOL = "https"
     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."""
         """Create server by wrapping HTTP socket in an SSL socket."""
         log.log(10, "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
         # Fails with Python 2.5, import if needed
@@ -108,15 +124,17 @@ class HTTPSServer(HTTPServer):
         import ssl
         import ssl
         # pylint: enable=F0401
         # pylint: enable=F0401
 
 
-        HTTPServer.__init__(self, address, handler)
+        HTTPServer.__init__(self, address, handler, False)
         self.socket = ssl.wrap_socket(
         self.socket = ssl.wrap_socket(
-            socket.socket(self.address_family, self.socket_type),
+            self.socket,
             server_side=True,
             server_side=True,
             certfile=config.get("server", "certificate"),
             certfile=config.get("server", "certificate"),
             keyfile=config.get("server", "key"),
             keyfile=config.get("server", "key"),
             ssl_version=ssl.PROTOCOL_SSLv23)
             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):
 class CalendarHTTPHandler(server.BaseHTTPRequestHandler):

+ 1 - 2
radicale/config.py

@@ -39,8 +39,7 @@ except ImportError:
 # Default configuration
 # Default configuration
 INITIAL_CONFIG = {
 INITIAL_CONFIG = {
     "server": {
     "server": {
-        "host": "",
-        "port": "5232",
+        "hosts": "0.0.0.0:5232",
         "daemon": "False",
         "daemon": "False",
         "ssl": "False",
         "ssl": "False",
         "certificate": "/etc/apache2/ssl/server.crt",
         "certificate": "/etc/apache2/ssl/server.crt",

+ 3 - 2
setup.py

@@ -27,8 +27,8 @@ it requires few software dependances and is pre-configured to work
 out-of-the-box.
 out-of-the-box.
 
 
 The Radicale Project runs on most of the UNIX-like platforms (Linux, BSD,
 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.
 version 3.
 
 
 For further information, please visit the `Radicale Website
 For further information, please visit the `Radicale Website
@@ -91,4 +91,5 @@ setup(
         "Programming Language :: Python :: 3",
         "Programming Language :: Python :: 3",
         "Programming Language :: Python :: 3.0",
         "Programming Language :: Python :: 3.0",
         "Programming Language :: Python :: 3.1",
         "Programming Language :: Python :: 3.1",
+        "Programming Language :: Python :: 3.2",
         "Topic :: Office/Business :: Groupware"])
         "Topic :: Office/Business :: Groupware"])