Hi, I have prepared security updates for radicale in Wheezy and Jessie. This is Debian bug #809920 [1]. I have tested both patches and they were reviewed by upstream. The debdiffs are attached to this e-mail.
Regards, Markus [1] https://bugs.debian.org/809920
diff -Nru radicale-0.9/debian/changelog radicale-0.9/debian/changelog --- radicale-0.9/debian/changelog 2014-08-25 02:45:58.000000000 +0200 +++ radicale-0.9/debian/changelog 2016-01-28 17:50:28.000000000 +0100 @@ -1,3 +1,13 @@ +radicale (0.9-1+deb8u1) jessie-security; urgency=high + + * Non-maintainer upload by the Security Team. + * CVE-2015-8748 and CVE-2015-8747: + Fix insecure path handling by sanitizing system paths and always + making them absolute. Fix multifilesystem backend allowed access + to arbitrary files on all platforms. (Closes: #809920) + + -- Markus Koschany <a...@debian.org> Thu, 28 Jan 2016 01:02:54 +0100 + radicale (0.9-1) unstable; urgency=medium [ upstream ] diff -Nru radicale-0.9/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch radicale-0.9/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch --- radicale-0.9/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch 1970-01-01 01:00:00.000000000 +0100 +++ radicale-0.9/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch 2016-01-28 17:50:28.000000000 +0100 @@ -0,0 +1,415 @@ +From: Markus Koschany <a...@debian.org> +Date: Thu, 28 Jan 2016 01:00:41 +0100 +Subject: CVE-2015-8748 and CVE-2015-8747 + +Fix insecure path handling by sanitizing system paths and always +making them absolute. Fix multifilesystem backend allowed access +to arbitrary files on all platforms. + +The patch was kindly reviewed by upstream. + +Origin: https://github.com/Kozea/Radicale/pull/343/commits +Debian-Bug: https://bugs.debian.org/809920 +--- + radicale/__init__.py | 26 ++++++------ + radicale/ical.py | 11 ++--- + radicale/pathutils.py | 84 +++++++++++++++++++++++++++++++++++++ + radicale/storage/filesystem.py | 41 +++++++++++------- + radicale/storage/multifilesystem.py | 47 ++++++++++++++------- + 5 files changed, 160 insertions(+), 49 deletions(-) + create mode 100644 radicale/pathutils.py + +diff --git a/radicale/__init__.py b/radicale/__init__.py +index 2824efd..694a447 100644 +--- a/radicale/__init__.py ++++ b/radicale/__init__.py +@@ -32,7 +32,6 @@ import os + import sys + import pprint + import base64 +-import posixpath + import socket + import ssl + import wsgiref.simple_server +@@ -47,7 +46,7 @@ except ImportError: + from urlparse import urlparse + # pylint: enable=F0401,E0611 + +-from . import auth, config, ical, log, rights, storage, xmlutils ++from . import auth, config, ical, log, pathutils, rights, storage, xmlutils + + + VERSION = "0.9" +@@ -174,12 +173,9 @@ class Application(object): + + @staticmethod + def sanitize_uri(uri): +- """Unquote and remove /../ to prevent access to other data.""" ++ """Unquote and make absolute to prevent access to other data.""" + uri = unquote(uri) +- trailing_slash = "/" if uri.endswith("/") else "" +- uri = posixpath.normpath(uri) +- trailing_slash = "" if uri == "/" else trailing_slash +- return uri + trailing_slash ++ return pathutils.sanitize_path(uri) + + def collect_allowed_items(self, items, user): + """Get items from request that user is allowed to access.""" +@@ -248,18 +244,21 @@ class Application(object): + + base_prefix = config.get("server", "base_prefix") + if environ["PATH_INFO"].startswith(base_prefix): +- # Sanitize request URI +- environ["PATH_INFO"] = self.sanitize_uri( +- "/%s" % environ["PATH_INFO"][len(base_prefix):]) +- log.LOGGER.debug("Sanitized path: %s", environ["PATH_INFO"]) ++ environ["PATH_INFO"] = environ["PATH_INFO"][len(base_prefix):] + elif config.get("server", "can_skip_base_prefix"): + log.LOGGER.debug( +- "Skipped already sanitized path: %s", environ["PATH_INFO"]) ++ "Prefix already stripped from path: %s", environ["PATH_INFO"]) + else: + # Request path not starting with base_prefix, not allowed + log.LOGGER.debug( + "Path not starting with prefix: %s", environ["PATH_INFO"]) +- environ["PATH_INFO"] = None ++ status, headers, _ = NOT_ALLOWED ++ start_response(status, list(headers.items())) ++ return [] ++ ++ # Sanitize request URI ++ environ["PATH_INFO"] = self.sanitize_uri(environ["PATH_INFO"]) ++ log.LOGGER.debug("Sanitized path: %s", environ["PATH_INFO"]) + + # Get content + content_length = int(environ.get("CONTENT_LENGTH") or 0) +@@ -270,6 +269,7 @@ class Application(object): + else: + content = None + ++ + path = environ["PATH_INFO"] + + # Get function corresponding to method +diff --git a/radicale/ical.py b/radicale/ical.py +index a9db439..40e8793 100644 +--- a/radicale/ical.py ++++ b/radicale/ical.py +@@ -26,11 +26,11 @@ Define the main classes of a collection as seen from the server. + """ + + import os +-import posixpath + import hashlib + from uuid import uuid4 + from random import randint + from contextlib import contextmanager ++from . import pathutils + + + def serialize(tag, headers=(), items=()): +@@ -188,8 +188,9 @@ class Collection(object): + + """ + self.encoding = "utf-8" +- split_path = path.split("/") +- self.path = path if path != "." else "" ++ # path should already be sanitized ++ self.path = pathutils.sanitize_path(path).strip("/") ++ split_path = self.path.split("/") + if principal and split_path and self.is_node(self.path): + # Already existing principal collection + self.owner = split_path[0] +@@ -219,8 +220,8 @@ class Collection(object): + if path is None: + return [] + +- # First do normpath and then strip, to prevent access to FOLDER/../ +- sane_path = posixpath.normpath(path.replace(os.sep, "/")).strip("/") ++ # path should already be sanitized ++ sane_path = pathutils.sanitize_path(path).strip("/") + attributes = sane_path.split("/") + if not attributes: + return [] +diff --git a/radicale/pathutils.py b/radicale/pathutils.py +new file mode 100644 +index 0000000..2aa13af +--- /dev/null ++++ b/radicale/pathutils.py +@@ -0,0 +1,84 @@ ++# -*- coding: utf-8 -*- ++# ++# This file is part of Radicale Server - Calendar Server ++# ++# 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 <http://www.gnu.org/licenses/>. ++ ++""" ++Helper functions for working with paths ++ ++""" ++ ++import os ++import posixpath ++ ++from . import log ++ ++ ++def sanitize_path(path): ++ """Make absolute (with leading slash) to prevent access to other data. ++ Preserves an potential trailing slash.""" ++ trailing_slash = "/" if path.endswith("/") else "" ++ path = posixpath.normpath(path) ++ new_path = "/" ++ for part in path.split("/"): ++ if not part or part in (".", ".."): ++ continue ++ new_path = posixpath.join(new_path, part) ++ trailing_slash = "" if new_path.endswith("/") else trailing_slash ++ return new_path + trailing_slash ++ ++ ++def is_safe_path_component(path): ++ """Checks if path is a single component of a path and is safe to join""" ++ if not path: ++ return False ++ head, _ = posixpath.split(path) ++ if head: ++ return False ++ if path in (".", ".."): ++ return False ++ return True ++ ++ ++def is_safe_filesystem_path_component(path): ++ """Checks if path is a single component of a local filesystem path ++ and is safe to join""" ++ if not path: ++ return False ++ drive, _ = os.path.splitdrive(path) ++ if drive: ++ return False ++ head, _ = os.path.split(path) ++ if head: ++ return False ++ if path in (os.curdir, os.pardir): ++ return False ++ return True ++ ++ ++def path_to_filesystem(path, base_folder): ++ """Converts path to a local filesystem path relative to base_folder ++ in a secure manner or raises ValueError.""" ++ sane_path = sanitize_path(path).strip("/") ++ safe_path = base_folder ++ if not sane_path: ++ return safe_path ++ for part in sane_path.split("/"): ++ if not is_safe_filesystem_path_component(part): ++ log.LOGGER.debug("Can't translate path safely to filesystem: %s", ++ path) ++ raise ValueError("Unsafe path") ++ safe_path = os.path.join(safe_path, part) ++ return safe_path +\ No newline at end of file +diff --git a/radicale/storage/filesystem.py b/radicale/storage/filesystem.py +index 238f6ec..2363547 100644 +--- a/radicale/storage/filesystem.py ++++ b/radicale/storage/filesystem.py +@@ -28,7 +28,8 @@ import json + import time + import sys + from contextlib import contextmanager +-from .. import config, ical ++ ++from .. import config, ical, log, pathutils + + + FOLDER = os.path.expanduser(config.get("storage", "filesystem_folder")) +@@ -62,59 +63,67 @@ def open(path, mode="r"): + class Collection(ical.Collection): + """Collection stored in a flat ical file.""" + @property +- def _path(self): ++ def _filesystem_path(self): + """Absolute path of the file at local ``path``.""" +- return os.path.join(FOLDER, self.path.replace("/", os.sep)) ++ return pathutils.path_to_filesystem(self.path, FOLDER) + + @property + def _props_path(self): + """Absolute path of the file storing the collection properties.""" +- return self._path + ".props" ++ return self._filesystem_path + ".props" + + def _create_dirs(self): + """Create folder storing the collection if absent.""" +- if not os.path.exists(os.path.dirname(self._path)): +- os.makedirs(os.path.dirname(self._path)) ++ if not os.path.exists(os.path.dirname(self._filesystem_path)): ++ os.makedirs(os.path.dirname(self._filesystem_path)) + + def save(self, text): + self._create_dirs() +- with open(self._path, "w") as fd: ++ with open(self._filesystem_path, "w") as fd: + fd.write(text) + + def delete(self): +- os.remove(self._path) ++ os.remove(self._filesystem_path) + os.remove(self._props_path) + + @property + def text(self): + try: +- with open(self._path) as fd: ++ with open(self._filesystem_path) as fd: + return fd.read() + except IOError: + return "" + + @classmethod + def children(cls, path): +- abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) +- _, directories, files = next(os.walk(abs_path)) ++ filesystem_path = pathutils.path_to_filesystem(path, FOLDER) ++ _, directories, files = next(os.walk(filesystem_path)) + for filename in directories + files: ++ # make sure that the local filename can be translated ++ # into an internal path ++ if not pathutils.is_safe_path_component(filename): ++ log.LOGGER.debug("Skipping unsupported filename: %s", ++ filename) ++ continue + rel_filename = posixpath.join(path, filename) + if cls.is_node(rel_filename) or cls.is_leaf(rel_filename): + yield cls(rel_filename) + + @classmethod + def is_node(cls, path): +- abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) +- return os.path.isdir(abs_path) ++ filesystem_path = pathutils.path_to_filesystem(path, FOLDER) ++ return os.path.isdir(filesystem_path) + + @classmethod + def is_leaf(cls, path): +- abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) +- return os.path.isfile(abs_path) and not abs_path.endswith(".props") ++ filesystem_path = pathutils.path_to_filesystem(path, FOLDER) ++ return (os.path.isfile(filesystem_path) and not ++ filesystem_path.endswith(".props")) + + @property + def last_modified(self): +- modification_time = time.gmtime(os.path.getmtime(self._path)) ++ modification_time = \ ++ time.gmtime(os.path.getmtime(self._filesystem_path)) + return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time) + + @property +diff --git a/radicale/storage/multifilesystem.py b/radicale/storage/multifilesystem.py +index ca2d70c..f3e0476 100644 +--- a/radicale/storage/multifilesystem.py ++++ b/radicale/storage/multifilesystem.py +@@ -29,13 +29,14 @@ import sys + + from . import filesystem + from .. import ical ++from .. import pathutils + + + class Collection(filesystem.Collection): + """Collection stored in several files per calendar.""" + def _create_dirs(self): +- if not os.path.exists(self._path): +- os.makedirs(self._path) ++ if not os.path.exists(self._filesystem_path): ++ os.makedirs(self._filesystem_path) + + @property + def headers(self): +@@ -54,16 +55,27 @@ class Collection(filesystem.Collection): + name = ( + component.name if sys.version_info[0] >= 3 else + component.name.encode(filesystem.FILESYSTEM_ENCODING)) +- path = os.path.join(self._path, name) +- with filesystem.open(path, "w") as fd: ++ if not pathutils.is_safe_filesystem_path_component(name): ++ log.LOGGER.debug( ++ "Can't tranlate name safely to filesystem, " ++ "skipping component: %s", name) ++ continue ++ filesystem_path = os.path.join(self._filesystem_path, name) ++ with filesystem.open(filesystem_path, "w") as fd: + fd.write(text) + + def delete(self): +- shutil.rmtree(self._path) ++ shutil.rmtree(self._filesystem_path) + + def remove(self, name): +- if os.path.exists(os.path.join(self._path, name)): +- os.remove(os.path.join(self._path, name)) ++ if not pathutils.is_safe_filesystem_path_component(name): ++ log.LOGGER.debug( ++ "Can't tranlate name safely to filesystem, " ++ "skipping component: %s", name) ++ return ++ filesystem_path = os.path.join(self._filesystem_path, name) ++ if os.path.exists(filesystem_path): ++ os.remove(filesystem_path) + + @property + def text(self): +@@ -71,8 +83,9 @@ class Collection(filesystem.Collection): + ical.Timezone, ical.Event, ical.Todo, ical.Journal, ical.Card) + items = set() + try: +- for filename in os.listdir(self._path): +- with filesystem.open(os.path.join(self._path, filename)) as fd: ++ for filename in os.listdir(self._filesystem_path): ++ filesystem_path = os.path.join(self._filesystem_path, filename) ++ with filesystem.open(filesystem_path, "w") as fd: + items.update(self._parse(fd.read(), components)) + except IOError: + return "" +@@ -82,17 +95,21 @@ class Collection(filesystem.Collection): + + @classmethod + def is_node(cls, path): +- path = os.path.join(filesystem.FOLDER, path.replace("/", os.sep)) +- return os.path.isdir(path) and not os.path.exists(path + ".props") ++ filesystem_path = pathutils.path_to_filesystem(path, ++ filesystem.FOLDER) ++ return (os.path.isdir(filesystem_path) and ++ not os.path.exists(filesystem_path + ".props")) + + @classmethod + def is_leaf(cls, path): +- path = os.path.join(filesystem.FOLDER, path.replace("/", os.sep)) +- return os.path.isdir(path) and os.path.exists(path + ".props") ++ filesystem_path = pathutils.path_to_filesystem(path, ++ filesystem.FOLDER) ++ return (os.path.isdir(filesystem_path) and ++ os.path.exists(path + ".props")) + + @property + def last_modified(self): + last = max([ +- os.path.getmtime(os.path.join(self._path, filename)) +- for filename in os.listdir(self._path)] or [0]) ++ os.path.getmtime(os.path.join(self._filesystem_path, filename)) ++ for filename in os.listdir(self._filesystem_path)] or [0]) + return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(last)) diff -Nru radicale-0.9/debian/patches/series radicale-0.9/debian/patches/series --- radicale-0.9/debian/patches/series 2013-12-28 02:59:42.000000000 +0100 +++ radicale-0.9/debian/patches/series 2016-01-28 17:50:28.000000000 +0100 @@ -1,3 +1,4 @@ 1001_comment_out_defaults.patch 1002_daemon-owned_logdir.patch 2001_debianize_config.patch +CVE-2015-8748-and-CVE-2015-8747.patch
diff -Nru radicale-0.7/debian/changelog radicale-0.7/debian/changelog --- radicale-0.7/debian/changelog 2013-02-11 14:06:19.000000000 +0100 +++ radicale-0.7/debian/changelog 2016-01-28 17:55:21.000000000 +0100 @@ -1,3 +1,14 @@ +radicale (0.7-1.1+deb7u1) wheezy-security; urgency=high + + * Non-maintainer upload by the Security Team. + * CVE-2015-8748 and CVE-2015-8747: + Fix insecure path handling by sanitizing system paths and always + making them absolute. Version 0.7 of Radicale is only partly affected by + CVE-2015-8747 because the multifilesystem storage does not exist in + this version. (Closes: #809920) + + -- Markus Koschany <a...@debian.org> Thu, 28 Jan 2016 14:42:55 +0100 + radicale (0.7-1.1) unstable; urgency=low * Non-maintainer upload. (Acknowledged by Jonas Smedegaard) diff -Nru radicale-0.7/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch radicale-0.7/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch --- radicale-0.7/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch 1970-01-01 01:00:00.000000000 +0100 +++ radicale-0.7/debian/patches/CVE-2015-8748-and-CVE-2015-8747.patch 2016-01-28 17:55:21.000000000 +0100 @@ -0,0 +1,285 @@ +From: Markus Koschany <a...@debian.org> +Date: Thu, 28 Jan 2016 14:39:42 +0100 +Subject: CVE-2015-8748 and CVE-2015-8747 + +Fix insecure path handling by sanitizing system paths and always +making them absolute. The patch was kindly reviewed by upstream. Version 0.7 of +Radicale is only partly affected by CVE-2015-8747 because the multifilesystem storage +does not exist in this version. + +Origin: https://github.com/Kozea/Radicale/pull/343/commits +Debian-Bug: https://bugs.debian.org/809920 +--- + radicale/__init__.py | 9 ++--- + radicale/ical.py | 11 +++--- + radicale/pathutils.py | 84 ++++++++++++++++++++++++++++++++++++++++++ + radicale/storage/filesystem.py | 43 +++++++++++++-------- + 4 files changed, 120 insertions(+), 27 deletions(-) + create mode 100644 radicale/pathutils.py + +diff --git a/radicale/__init__.py b/radicale/__init__.py +index 2ed1b1e..fe71e83 100644 +--- a/radicale/__init__.py ++++ b/radicale/__init__.py +@@ -31,7 +31,6 @@ should have been included in this package. + import os + import pprint + import base64 +-import posixpath + import socket + import ssl + import wsgiref.simple_server +@@ -47,6 +46,7 @@ except ImportError: + # pylint: enable=F0401,E0611 + + from radicale import acl, config, ical, log, storage, xmlutils ++from . import pathutils + + + VERSION = "0.7" +@@ -162,12 +162,9 @@ class Application(object): + + @staticmethod + def sanitize_uri(uri): +- """Unquote and remove /../ to prevent access to other data.""" ++ """Unquote and make absolute to prevent access to other data.""" + uri = unquote(uri) +- trailing_slash = "/" if uri.endswith("/") else "" +- uri = posixpath.normpath(uri) +- trailing_slash = "" if uri == "/" else trailing_slash +- return uri + trailing_slash ++ return pathutils.sanitize_path(uri) + + def __call__(self, environ, start_response): + """Manage a request.""" +diff --git a/radicale/ical.py b/radicale/ical.py +index d4608c7..f3a4357 100644 +--- a/radicale/ical.py ++++ b/radicale/ical.py +@@ -26,9 +26,9 @@ Define the main classes of a collection as seen from the server. + """ + + import os +-import posixpath + import uuid + from contextlib import contextmanager ++from . import pathutils + + + def serialize(tag, headers=(), items=()): +@@ -176,8 +176,9 @@ class Collection(object): + + """ + self.encoding = "utf-8" +- split_path = path.split("/") +- self.path = path if path != "." else "" ++ # path should already be sanitized ++ self.path = pathutils.sanitize_path(path).strip("/") ++ split_path = self.path.split("/") + if principal and split_path and self.is_node(self.path): + # Already existing principal collection + self.owner = split_path[0] +@@ -203,8 +204,8 @@ class Collection(object): + The ``path`` is relative. + + """ +- # First do normpath and then strip, to prevent access to FOLDER/../ +- sane_path = posixpath.normpath(path.replace(os.sep, "/")).strip("/") ++ # path should already be sanitized ++ sane_path = pathutils.sanitize_path(path).strip("/") + attributes = sane_path.split("/") + if not attributes: + return None +diff --git a/radicale/pathutils.py b/radicale/pathutils.py +new file mode 100644 +index 0000000..2aa13af +--- /dev/null ++++ b/radicale/pathutils.py +@@ -0,0 +1,84 @@ ++# -*- coding: utf-8 -*- ++# ++# This file is part of Radicale Server - Calendar Server ++# ++# 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 <http://www.gnu.org/licenses/>. ++ ++""" ++Helper functions for working with paths ++ ++""" ++ ++import os ++import posixpath ++ ++from . import log ++ ++ ++def sanitize_path(path): ++ """Make absolute (with leading slash) to prevent access to other data. ++ Preserves an potential trailing slash.""" ++ trailing_slash = "/" if path.endswith("/") else "" ++ path = posixpath.normpath(path) ++ new_path = "/" ++ for part in path.split("/"): ++ if not part or part in (".", ".."): ++ continue ++ new_path = posixpath.join(new_path, part) ++ trailing_slash = "" if new_path.endswith("/") else trailing_slash ++ return new_path + trailing_slash ++ ++ ++def is_safe_path_component(path): ++ """Checks if path is a single component of a path and is safe to join""" ++ if not path: ++ return False ++ head, _ = posixpath.split(path) ++ if head: ++ return False ++ if path in (".", ".."): ++ return False ++ return True ++ ++ ++def is_safe_filesystem_path_component(path): ++ """Checks if path is a single component of a local filesystem path ++ and is safe to join""" ++ if not path: ++ return False ++ drive, _ = os.path.splitdrive(path) ++ if drive: ++ return False ++ head, _ = os.path.split(path) ++ if head: ++ return False ++ if path in (os.curdir, os.pardir): ++ return False ++ return True ++ ++ ++def path_to_filesystem(path, base_folder): ++ """Converts path to a local filesystem path relative to base_folder ++ in a secure manner or raises ValueError.""" ++ sane_path = sanitize_path(path).strip("/") ++ safe_path = base_folder ++ if not sane_path: ++ return safe_path ++ for part in sane_path.split("/"): ++ if not is_safe_filesystem_path_component(part): ++ log.LOGGER.debug("Can't translate path safely to filesystem: %s", ++ path) ++ raise ValueError("Unsafe path") ++ safe_path = os.path.join(safe_path, part) ++ return safe_path +\ No newline at end of file +diff --git a/radicale/storage/filesystem.py b/radicale/storage/filesystem.py +index 546a32f..d7ffa87 100644 +--- a/radicale/storage/filesystem.py ++++ b/radicale/storage/filesystem.py +@@ -23,12 +23,13 @@ Filesystem storage backend. + + import codecs + import os +-import posixpath + import json + import time + from contextlib import contextmanager + + from radicale import config, ical ++from .. import pathutils ++ + + + FOLDER = os.path.expanduser(config.get("storage", "filesystem_folder")) +@@ -46,57 +47,67 @@ def open(path, mode="r"): + class Collection(ical.Collection): + """Collection stored in a flat ical file.""" + @property +- def _path(self): ++ def _filesystem_path(self): + """Absolute path of the file at local ``path``.""" +- return os.path.join(FOLDER, self.path.replace("/", os.sep)) ++ return pathutils.path_to_filesystem(self.path, FOLDER) + + @property + def _props_path(self): + """Absolute path of the file storing the collection properties.""" +- return self._path + ".props" ++ return self._filesystem_path + ".props" + + def _create_dirs(self): + """Create folder storing the collection if absent.""" +- if not os.path.exists(os.path.dirname(self._path)): +- os.makedirs(os.path.dirname(self._path)) ++ if not os.path.exists(os.path.dirname(self._filesystem_path)): ++ os.makedirs(os.path.dirname(self._filesystem_path)) + + def save(self, text): + self._create_dirs() +- open(self._path, "w").write(text) ++ with open(self._filesystem_path, "w") as fd: ++ fd.write(text) + + def delete(self): +- os.remove(self._path) ++ os.remove(self._filesystem_path) + os.remove(self._props_path) + + @property + def text(self): + try: +- return open(self._path).read() ++ with open(self._filesystem_path) as fd: ++ return fd.read() + except IOError: + return "" + + @classmethod + def children(cls, path): +- abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) +- _, directories, files = next(os.walk(abs_path)) ++ filesystem_path = pathutils.path_to_filesystem(path, FOLDER) ++ _, directories, files = next(os.walk(filesystem_path)) + for filename in directories + files: ++ # make sure that the local filename can be translated ++ # into an internal path ++ if not pathutils.is_safe_path_component(filename): ++ log.LOGGER.debug("Skipping unsupported filename: %s", ++ filename) ++ continue + rel_filename = posixpath.join(path, filename) + if cls.is_node(rel_filename) or cls.is_leaf(rel_filename): + yield cls(rel_filename) + + @classmethod + def is_node(cls, path): +- abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) +- return os.path.isdir(abs_path) ++ filesystem_path = pathutils.path_to_filesystem(path, FOLDER) ++ return os.path.isdir(filesystem_path) + + @classmethod + def is_leaf(cls, path): +- abs_path = os.path.join(FOLDER, path.replace("/", os.sep)) +- return os.path.isfile(abs_path) and not abs_path.endswith(".props") ++ filesystem_path = pathutils.path_to_filesystem(path, FOLDER) ++ return (os.path.isfile(filesystem_path) and not ++ filesystem_path.endswith(".props")) + + @property + def last_modified(self): +- modification_time = time.gmtime(os.path.getmtime(self._path)) ++ modification_time = \ ++ time.gmtime(os.path.getmtime(self._filesystem_path)) + return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time) + + @property diff -Nru radicale-0.7/debian/patches/series radicale-0.7/debian/patches/series --- radicale-0.7/debian/patches/series 2013-02-11 14:06:19.000000000 +0100 +++ radicale-0.7/debian/patches/series 2016-01-28 17:55:21.000000000 +0100 @@ -3,3 +3,4 @@ 1001_comment_out_defaults.patch 1002_daemon-owned_logdir.patch 2001_debianize_config.patch +CVE-2015-8748-and-CVE-2015-8747.patch
signature.asc
Description: OpenPGP digital signature