diff --git a/.project b/.project new file mode 100644 index 00000000..94930993 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Radicale + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 00000000..09e36056 --- /dev/null +++ b/.pydevproject @@ -0,0 +1,10 @@ + + + + + +/Radicale + +python 2.7 +Default + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..864a84bd --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +#Sat Aug 04 10:58:22 CEST 2012 +eclipse.preferences.version=1 +encoding//radicale/__init__.py=utf-8 +encoding//radicale/__main__.py=utf-8 diff --git a/NEWS.rst b/NEWS.rst index 93c9be0b..7bbc28e7 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -3,6 +3,18 @@ ====== +0.7.1 - Waterfalls +================== + +* Many address books fixes +* New IMAP ACL (by Daniel Aleksandersen) +* PAM ACL fixed (by Daniel Aleksandersen) +* Courier ACL fixed (by Benjamin Frank) +* Always set display name to collections (by Oskari Timperi) +* Various DELETE responses fixed + + + 0.7 - Eternal Sunshine ====================== diff --git a/radicale/__init__.py b/radicale/__init__.py index 7a24859b..d88af77c 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -172,7 +172,7 @@ class Application(object): def __call__(self, environ, start_response): """Manage a request.""" log.LOGGER.info("%s request at %s received" % ( - environ["REQUEST_METHOD"], environ["PATH_INFO"])) + environ["REQUEST_METHOD"], environ["PATH_INFO"])) headers = pprint.pformat(self.headers_log(environ)) log.LOGGER.debug("Request headers:\n%s" % headers) @@ -216,20 +216,29 @@ class Application(object): if access.is_authenticated(user, password): - collections = [] - for collection in items: - log.LOGGER.debug("Testing %s" % (collection.name)) - if not isinstance(collection, ical.Collection): - log.LOGGER.info("not a collection: " + collection.name) + last_collection_allowed = None + allowed_items = [] + for item in items: + log.LOGGER.debug("Testing %s" % (item.name)) + if not isinstance(item, ical.Collection): + # item is not a colleciton, it's the child of the last + # collection we've met in the loop. Only add this item if + # this last collection was allowed. log.LOGGER.info("not a collection: " + collection.name) # collections.append(collection) - elif access.may_read(user, collection) or access.may_write(user, collection): - log.LOGGER.info("Has access to " + collection.name) - collections.append(collection) + if last_collection_allowed: + allowed_items.append(item) + else: + if access.may_read(user, item) or access.may_write(user, item): + log.LOGGER.info(user + "has access to " + item.name) + last_collection_allowed = True + allowed_items.append(item) + else: + last_collection_allowed = False - if collections: + if allowed_items: # Collections found status, headers, answer = function( - environ, collections, content, user) + environ, allowed_items, content, user) else: # Good user and no collections found, redirect user to home location = "/%s/" % str(quote(user)) @@ -241,7 +250,7 @@ class Application(object): else: # Send answer anyway since else we're getting into a redirect loop status, headers, answer = function( - environ, collections, content, user) + environ, allowed_items, content, user) else: @@ -499,7 +508,7 @@ class Application(object): # Evolution bug workaround etag = environ.get("HTTP_IF_MATCH", "").replace("\\", "") if (not item and not etag) or ( - item and ((etag or item.etag) == item.etag)): + item and ((etag or item.etag) == item.etag)): # PUT allowed in 3 cases # Case 1: No item and no ETag precondition: Add new item # Case 2: Item and ETag precondition verified: Modify item diff --git a/radicale/__main__.py b/radicale/__main__.py index b2a0da6f..175f6133 100644 --- a/radicale/__main__.py +++ b/radicale/__main__.py @@ -91,6 +91,8 @@ def run(): # Fork if Radicale is launched as daemon if options.daemon: + if options.pid and os.path.exists(options.pid): + raise OSError("PID file exists: %s" % options.pid) pid = os.fork() if pid: try: diff --git a/radicale/acl/courier.py b/radicale/acl/courier.py index 21241c1f..c5bd7b97 100644 --- a/radicale/acl/courier.py +++ b/radicale/acl/courier.py @@ -33,7 +33,7 @@ def is_authenticated(user, password): """Check if ``user``/``password`` couple is valid.""" line = "%s\nlogin\n%s\n%s" % (sys.argv[0], user, password) - line = "%i\n%s" % (len(line), line) + line = "AUTH %i\n%s" % (len(line), line) try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(COURIER_SOCKET) @@ -48,7 +48,13 @@ def is_authenticated(user, password): log.LOGGER.debug("Got Courier socket response: %r" % data) - if repr(data) == "FAIL": - return False + # Address, HOME, GID, and either UID or USERNAME are mandatory in resposne + # see http://www.courier-mta.org/authlib/README_authlib.html#authpipeproto + for line in data.split(): + if 'GID' in line: + return True - return True + # default is reject + # this alleviates the problem of a possibly empty reply from authlib + # see http://www.courier-mta.org/authlib/README_authlib.html#authpipeproto + return False diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index d72aa492..6d3a92ac 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -270,8 +270,8 @@ def _propfind_response(path, item, props, user): element.append(privilege) elif tag == _tag("D", "supported-report-set"): for report_name in ( - "principal-property-search", "sync-collection" - "expand-property", "principal-search-property-set"): + "principal-property-search", "sync-collection" + "expand-property", "principal-search-property-set"): supported = ET.Element(_tag("D", "supported-report")) report_tag = ET.Element(_tag("D", "report")) report_tag.text = report_name @@ -422,9 +422,8 @@ def report(path, xml_request, collection): props = [prop.tag for prop in prop_element] if collection: - if root.tag in ( - _tag("C", "calendar-multiget"), - _tag("CR", "addressbook-multiget")): + if root.tag in (_tag("C", "calendar-multiget"), + _tag("CR", "addressbook-multiget")): # Read rfc4791-7.9 for info hreferences = set( href_element.text for href_element @@ -472,8 +471,8 @@ def report(path, xml_request, collection): element = ET.Element(tag) if tag == _tag("D", "getetag"): element.text = item.etag - elif tag in ( - _tag("C", "calendar-data"), _tag("CR", "address-data")): + elif tag in (_tag("C", "calendar-data"), + _tag("CR", "address-data")): if isinstance(item, ical.Component): element.text = ical.serialize( collection_tag, collection_headers,