diff --git a/NEWS b/NEWS index 183ebeb7..491d0a02 100644 --- a/NEWS +++ b/NEWS @@ -14,7 +14,8 @@ * Smart, verbose and configurable logs * Apple iCal 4 and iPhone support (by Łukasz Langa) * LDAP auth backend (by Corentin Le Bail) -* Owner-less calendars (by René Neumann) +* Public and private calendars (by René Neumann) +* PID file * Journal entries support * Drop Python 2.5 support diff --git a/config b/config index 49e4d90f..8f7b6163 100644 --- a/config +++ b/config @@ -36,6 +36,10 @@ stock = utf-8 # Access method # Value: None | htpasswd | LDAP type = None +# Usernames used for public calendars, separated by a comma +public_users = public +# Usernames used for private calendars, separated by a comma +private_users = private # Htpasswd filename htpasswd_filename = /etc/radicale/users # Htpasswd encryption method diff --git a/radicale.py b/radicale.py index a5349b57..ed88a4a6 100755 --- a/radicale.py +++ b/radicale.py @@ -32,6 +32,7 @@ Launch the server according to configuration and command-line options. """ +import atexit import os import sys import optparse @@ -101,6 +102,14 @@ if options.daemon: sys.exit() sys.stdout = sys.stderr = open(os.devnull, "w") +# Register exit function +def cleanup(): + radicale.log.LOGGER.debug("Cleaning up") + # Remove PID file + if options.pid and options.daemon: + os.unlink(options.pid) + +atexit.register(cleanup) radicale.log.LOGGER.info("Starting Radicale") # Create calendar servers diff --git a/radicale.wsgi b/radicale.wsgi new file mode 100755 index 00000000..3f0d0dc4 --- /dev/null +++ b/radicale.wsgi @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# This file is part of Radicale Server - Calendar Server +# Copyright © 2011 Guillaume Ayoub +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Radicale. If not, see . + +""" +Radicale WSGI file (mod_wsgi and uWSGI compliant). + +""" + +import radicale +application = radicale.Application() diff --git a/radicale/__init__.py b/radicale/__init__.py index 491846b6..c82c1252 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -183,16 +183,24 @@ class Application(object): if last_allowed: calendars.append(calendar) continue - log.LOGGER.info( - "Checking rights for calendar owned by %s" % calendar.owner) - if self.acl.has_right(calendar.owner, user, password): - log.LOGGER.info("%s allowed" % (user or "anonymous user")) + if calendar.owner in acl.PUBLIC_USERS: + log.LOGGER.info("Public calendar") calendars.append(calendar) last_allowed = True else: - log.LOGGER.info("%s refused" % (user or "anonymous user")) - last_allowed = False + log.LOGGER.info( + "Checking rights for calendar owned by %s" % ( + calendar.owner or "nobody")) + if self.acl.has_right(calendar.owner, user, password): + log.LOGGER.info( + "%s allowed" % (user or "Anonymous user")) + calendars.append(calendar) + last_allowed = True + else: + log.LOGGER.info( + "%s refused" % (user or "Anonymous user")) + last_allowed = False if calendars: status, headers, answer = function(environ, calendars, content) diff --git a/radicale/acl/LDAP.py b/radicale/acl/LDAP.py index 16162b96..dfde79f3 100644 --- a/radicale/acl/LDAP.py +++ b/radicale/acl/LDAP.py @@ -26,7 +26,7 @@ Authentication based on the ``python-ldap`` module """ import ldap -from radicale import config, log +from radicale import acl, config, log BASE = config.get("acl", "ldap_base") @@ -38,8 +38,8 @@ PASSWORD = config.get("acl", "ldap_password") def has_right(owner, user, password): """Check if ``user``/``password`` couple is valid.""" - if not user or (owner and user != owner): - # No user given, or owner is set and is not user, forbidden + if not user or (owner not in acl.PRIVATE_USERS and user != owner): + # No user given, or owner is not private and is not user, forbidden return False if BINDDN and PASSWORD: diff --git a/radicale/acl/__init__.py b/radicale/acl/__init__.py index 6745c76a..8886ae28 100644 --- a/radicale/acl/__init__.py +++ b/radicale/acl/__init__.py @@ -29,11 +29,29 @@ configuration. from radicale import config +PUBLIC_USERS = [] +PRIVATE_USERS = [] + + +def _config_users(name): + """Get an iterable of strings from the configuraton string [acl] ``name``. + + The values must be separated by a comma. The whitespace characters are + stripped at the beginning and at the end of the values. + + """ + for user in config.get("acl", name).split(","): + user = user.strip() + yield None if user == "None" else user + + def load(): """Load list of available ACL managers.""" acl_type = config.get("acl", "type") if acl_type == "None": return None else: + PUBLIC_USERS.extend(_config_users("public_users")) + PRIVATE_USERS.extend(_config_users("private_users")) module = __import__("radicale.acl", fromlist=[acl_type]) return getattr(module, acl_type) diff --git a/radicale/acl/htpasswd.py b/radicale/acl/htpasswd.py index d1000498..e624e91b 100644 --- a/radicale/acl/htpasswd.py +++ b/radicale/acl/htpasswd.py @@ -30,7 +30,7 @@ supported, but md5 is not (see ``htpasswd`` man page to understand why). import base64 import hashlib -from radicale import config +from radicale import acl, config FILENAME = config.get("acl", "htpasswd_filename") @@ -63,6 +63,6 @@ def has_right(owner, user, password): for line in open(FILENAME).readlines(): if line.strip(): login, hash_value = line.strip().split(":") - if login == user and (not owner or owner == user): + if login == user and (owner in acl.PRIVATE_USERS or owner == user): return globals()["_%s" % ENCRYPTION](hash_value, password) return False diff --git a/radicale/config.py b/radicale/config.py index c86ccb36..63cab7bd 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -50,6 +50,8 @@ INITIAL_CONFIG = { "stock": "utf-8"}, "acl": { "type": "None", + "public_users": "public", + "private_users": "private", "httpasswd_filename": "/etc/radicale/users", "httpasswd_encryption": "crypt", "ldap_url": "ldap://localhost:389/", @@ -74,6 +76,8 @@ for section, values in INITIAL_CONFIG.items(): _CONFIG_PARSER.read("/etc/radicale/config") _CONFIG_PARSER.read(os.path.expanduser("~/.config/radicale/config")) +if 'RADICALE_CONFIG' in os.environ: + _CONFIG_PARSER.read(os.environ['RADICALE_CONFIG']) # Wrap config module into ConfigParser instance sys.modules[__name__] = _CONFIG_PARSER diff --git a/setup.py b/setup.py index a95caa9f..6e686823 100755 --- a/setup.py +++ b/setup.py @@ -88,7 +88,6 @@ setup( "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Topic :: Office/Business :: Groupware"])