From 9514b62ed68588af4371569c9f09bad7ac8f1c7c Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Fri, 16 Jan 2015 18:25:51 +0100
Subject: [PATCH] lib-sieve: file storage: Restructured storage initialization
 to address backwards compatibility issues.

---
 .../storage/file/sieve-file-script.c          | 105 ++--
 .../storage/file/sieve-file-storage-active.c  |   9 +-
 .../storage/file/sieve-file-storage-list.c    |   1 +
 .../storage/file/sieve-file-storage-quota.c   |   1 +
 .../storage/file/sieve-file-storage-save.c    |   1 +
 .../storage/file/sieve-file-storage.c         | 542 ++++++++++++------
 .../storage/file/sieve-file-storage.h         |   3 +
 7 files changed, 433 insertions(+), 229 deletions(-)

diff --git a/src/lib-sieve/storage/file/sieve-file-script.c b/src/lib-sieve/storage/file/sieve-file-script.c
index 2acb1ccff..007da4e3b 100644
--- a/src/lib-sieve/storage/file/sieve-file-script.c
+++ b/src/lib-sieve/storage/file/sieve-file-script.c
@@ -52,7 +52,7 @@ const char *sieve_script_file_from_name(const char *name)
  */
 
 static void sieve_file_script_handle_error
-(struct sieve_file_script *fscript, const char *path,
+(struct sieve_file_script *fscript, const char *op, const char *path,
 	const char *name, enum sieve_error *error_r)
 {
 	struct sieve_script *script = &fscript->script;
@@ -67,12 +67,14 @@ static void sieve_file_script_handle_error
 		break;
 	case EACCES:
 		sieve_script_set_critical(script,
-			"Failed to stat sieve script: %s", eacces_error_get("stat", path));
+			"Failed to %s sieve script: %s",
+			op, eacces_error_get(op, path));
 		*error_r = SIEVE_ERROR_NO_PERMISSION;
 		break;
 	default:
 		sieve_script_set_critical(script,
-			"Failed to stat sieve script: stat(%s) failed: %m", path);
+			"Failed to %s sieve script: %s(%s) failed: %m",
+			op, op, path);
 		*error_r = SIEVE_ERROR_TEMP_FAILURE;
 		break;
 	}
@@ -150,14 +152,15 @@ struct sieve_file_script *sieve_file_script_init_from_name
 	struct sieve_storage *storage = &fstorage->storage;
 	struct sieve_file_script *fscript;
 
-	if (name != NULL) {
+	if (name != NULL && S_ISDIR(fstorage->st.st_mode)) {
 		return sieve_file_script_init_from_filename
 			(fstorage, sieve_script_file_from_name(name), name);
 	}
 
 	fscript = sieve_file_script_alloc();
 	sieve_script_init
-		(&fscript->script, storage, &sieve_file_script,	fstorage->path, NULL);
+		(&fscript->script, storage, &sieve_file_script,
+			fstorage->active_path, name);
 	return fscript;
 }
 
@@ -259,7 +262,7 @@ static int sieve_file_script_stat
 	if ( S_ISLNK(st->st_mode) && stat(path, st) < 0 )
 		return -1;
 
-	return 1;
+	return 0;
 }
 
 static const char *
@@ -289,67 +292,67 @@ static int sieve_file_script_open
 	pool_t pool = script->pool;
 	const char *filename, *name, *path;
 	const char *dirpath, *basename, *binpath, *binprefix;
-	struct stat st;
-	struct stat lnk_st;
+	struct stat st, lnk_st;
 	bool success = TRUE;
-	int ret;
+	int ret = 0;
 
 	filename = fscript->filename;
 	basename = NULL;
 	name = script->name;
-	path = fstorage->path;
+	st = fstorage->st;
+	lnk_st = fstorage->lnk_st;
 
 	if (name == NULL)
 		name = storage->script_name;
 
 	T_BEGIN {
-		if ( (ret=sieve_file_script_stat(path, &st, &lnk_st)) > 0 ) {
-			if ( S_ISDIR(st.st_mode) ) {
-				/* Path is directory */
-				if ( (filename == NULL || *filename == '\0') &&
-					name != NULL && *name != '\0' ) {
-					/* Name is used to find actual filename */
-					filename = sieve_script_file_from_name(name);
+		if ( S_ISDIR(st.st_mode) ) {
+			/* Storage is a directory */
+			path = fstorage->path;
+
+			if ( (filename == NULL || *filename == '\0') &&
+				name != NULL && *name != '\0' ) {
+				/* Name is used to find actual filename */
+				filename = sieve_script_file_from_name(name);
+				basename = name;
+			}
+			if ( filename == NULL || *filename == '\0' ) {
+				sieve_script_set_critical(script,
+					"Sieve script file path '%s' is a directory.", path);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				success = FALSE;
+			}	else {
+				/* Extend storage path with filename */
+				if (name == NULL) {
+					if ( basename == NULL &&
+						(basename=sieve_script_file_get_scriptname(filename)) == NULL )
+						basename = filename;
+					name = basename;
+				} else if (basename == NULL) {
 					basename = name;
 				}
-				if ( filename == NULL || *filename == '\0' ) {
-					sieve_script_set_critical(script,
-						"Sieve script file path '%s' is a directory.", path);
-					*error_r = SIEVE_ERROR_TEMP_FAILURE;
-					success = FALSE;
-				}	else {
-					/* Extend storage path with filename */
-					if (name == NULL) {
-						if ( basename == NULL &&
-							(basename=sieve_script_file_get_scriptname(filename)) == NULL )
-							basename = filename;
-						name = basename;
-					} else if (basename == NULL) {
-						basename = name;
-					}
-					dirpath = path;
+				dirpath = path;
 
-					path = sieve_file_storage_path_extend(fstorage, filename);
-					ret = sieve_file_script_stat(path, &st, &lnk_st);
-				}
+				path = sieve_file_storage_path_extend(fstorage, filename);
+				ret = sieve_file_script_stat(path, &st, &lnk_st);
+			}
 
-			} else {
+		} else {
+			/* Storage is a single file */
+			path = fstorage->active_path;
 
-				/* Extract filename from path */
-				filename = path_split_filename(path, &dirpath);
+			/* Extract filename from path */
+			filename = path_split_filename(path, &dirpath);
 
-				if ( (basename=sieve_script_file_get_scriptname(filename)) == NULL )
-					basename = filename;
+			if ( (basename=sieve_script_file_get_scriptname(filename)) == NULL )
+				basename = filename;
 
-				if ( name == NULL )
-					name = basename;
-			}
-		} else {
-			basename = name;
+			if ( name == NULL )
+				name = basename;
 		}
 
 		if ( success ) {
-			if ( ret <= 0 ) {
+			if ( ret < 0 ) {
 				/* Make sure we have a script name for the error */
 				if ( name == NULL ) {
 					if ( basename == NULL ) {
@@ -361,7 +364,8 @@ static int sieve_file_script_open
 					}
 					name = basename;
 				}
-				sieve_file_script_handle_error(fscript, path, name, error_r);
+				sieve_file_script_handle_error
+					(fscript, "stat", path, name, error_r);
 				success = FALSE;
 
 			} else if ( !S_ISREG(st.st_mode) ) {
@@ -431,7 +435,7 @@ static int sieve_file_script_get_stream
 
 	if ( (fd=open(fscript->path, O_RDONLY)) < 0 ) {
 		sieve_file_script_handle_error
-			(fscript, fscript->path, fscript->script.name, error_r);
+			(fscript, "open", fscript->path, fscript->script.name, error_r);
 		return -1;
 	}
 
@@ -599,6 +603,8 @@ static int _sieve_file_storage_script_activate
 	if ( ret <= 0 || strcmp(fscript->filename, afile) != 0 )
 		activated = 1;
 
+	i_assert( fstorage->link_path != NULL );
+
 	/* Check the scriptfile we are trying to activate */
 	if ( lstat(fscript->path, &st) != 0 ) {
 		sieve_script_set_critical(script,
@@ -684,6 +690,7 @@ static int sieve_file_storage_script_rename
 			/* Is the requested script active? */
 			if ( sieve_script_is_active(script) ) {
 				/* Active; make active link point to the new copy */
+				i_assert( fstorage->link_path != NULL );
 				link_path = t_strconcat
 					( fstorage->link_path, newfile, NULL );
 
diff --git a/src/lib-sieve/storage/file/sieve-file-storage-active.c b/src/lib-sieve/storage/file/sieve-file-storage-active.c
index e0a8ce99d..01a772163 100644
--- a/src/lib-sieve/storage/file/sieve-file-storage-active.c
+++ b/src/lib-sieve/storage/file/sieve-file-storage-active.c
@@ -133,6 +133,7 @@ static const char *sieve_file_storage_active_parse_link
 	}
 
 	/* Check whether the path is any good */
+	i_assert( fstorage->link_path != NULL );
 	if ( _file_path_cmp(scriptpath, fstorage->link_path) != 0 &&
 		_file_path_cmp(scriptpath, fstorage->path) != 0 ) {
 		sieve_storage_sys_warning(storage,
@@ -284,8 +285,12 @@ struct sieve_script *sieve_file_storage_active_script_open
 			return NULL;
 
 		/* Try to open the active_path as a regular file */
-		fscript = sieve_file_script_open_from_path(fstorage,
-			fstorage->active_path, NULL, NULL);
+		if ( S_ISDIR(fstorage->st.st_mode) ) {
+			fscript = sieve_file_script_open_from_path(fstorage,
+				fstorage->active_path, NULL, NULL);			
+		} else {
+			fscript = sieve_file_script_open_from_name(fstorage, NULL);
+		}
 		if ( fscript == NULL ) {
 			if ( storage->error_code != SIEVE_ERROR_NOT_FOUND ) {
 				sieve_storage_set_critical(storage,
diff --git a/src/lib-sieve/storage/file/sieve-file-storage-list.c b/src/lib-sieve/storage/file/sieve-file-storage-list.c
index 330903290..6bbe2382a 100644
--- a/src/lib-sieve/storage/file/sieve-file-storage-list.c
+++ b/src/lib-sieve/storage/file/sieve-file-storage-list.c
@@ -84,6 +84,7 @@ const char *sieve_file_storage_list_next
 			/* Don't list our active sieve script link if the link
 			 * resides in the script dir (generally a bad idea).
 			 */
+			i_assert( fstorage->link_path != NULL );
 			if ( *(fstorage->link_path) == '\0' &&
 				strcmp(fstorage->active_fname, dp->d_name) == 0 )
 				continue;
diff --git a/src/lib-sieve/storage/file/sieve-file-storage-quota.c b/src/lib-sieve/storage/file/sieve-file-storage-quota.c
index d6f14134a..089300565 100644
--- a/src/lib-sieve/storage/file/sieve-file-storage-quota.c
+++ b/src/lib-sieve/storage/file/sieve-file-storage-quota.c
@@ -60,6 +60,7 @@ int sieve_file_storage_quota_havespace
 		/* Don't list our active sieve script link if the link
 		 * resides in the script dir (generally a bad idea).
 		 */
+		i_assert( fstorage->link_path != NULL );
 		if ( *(fstorage->link_path) == '\0' &&
 			strcmp(fstorage->active_fname, dp->d_name) == 0 )
 			continue;
diff --git a/src/lib-sieve/storage/file/sieve-file-storage-save.c b/src/lib-sieve/storage/file/sieve-file-storage-save.c
index ea89c1e9e..02b004fba 100644
--- a/src/lib-sieve/storage/file/sieve-file-storage-save.c
+++ b/src/lib-sieve/storage/file/sieve-file-storage-save.c
@@ -184,6 +184,7 @@ sieve_file_storage_save_init(struct sieve_storage *storage,
 		/* Prevent overwriting the active script link when it resides in the
 		 * sieve storage directory.
 		 */
+		i_assert( fstorage->link_path != NULL );
 		if ( *(fstorage->link_path) == '\0' ) {
 			const char *svext;
 			size_t namelen;
diff --git a/src/lib-sieve/storage/file/sieve-file-storage.c b/src/lib-sieve/storage/file/sieve-file-storage.c
index 7e0767853..981ad24b8 100644
--- a/src/lib-sieve/storage/file/sieve-file-storage.c
+++ b/src/lib-sieve/storage/file/sieve-file-storage.c
@@ -2,6 +2,7 @@
  */
 
 #include "lib.h"
+#include "abspath.h"
 #include "home-expand.h"
 #include "ioloop.h"
 #include "mkdir-parents.h"
@@ -46,6 +47,46 @@ const char *sieve_file_storage_path_extend
  *
  */
 
+static int sieve_file_storage_stat
+(struct sieve_file_storage *fstorage, const char *path,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct stat st;
+
+	if ( lstat(path, &st) == 0 ) {
+		fstorage->lnk_st = st;
+
+		if ( !S_ISLNK(st.st_mode) || stat(path, &st) == 0 ) {
+			fstorage->st = st;
+			return 0;
+		}
+	}
+
+	switch ( errno ) {
+	case ENOENT:
+		sieve_storage_sys_debug(storage,
+			"Storage path `%s' not found", t_abspath(path));
+		sieve_storage_set_internal_error(storage); // should be overriden
+		*error_r = SIEVE_ERROR_NOT_FOUND;
+		break;
+	case EACCES:
+		sieve_storage_set_critical(storage,
+			"Failed to stat sieve storage path: %s",
+			eacces_error_get("stat", path));
+		*error_r = SIEVE_ERROR_NO_PERMISSION;
+		break;
+	default:
+		sieve_storage_set_critical(storage,
+			"Failed to stat sieve storage path: "
+			"stat(%s) failed: %m", path);
+		*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		break;
+	}
+
+	return -1;
+}
+
 static const char *sieve_storage_get_relative_link_path
 	(const char *active_path, const char *storage_dir)
 {
@@ -94,58 +135,6 @@ static mode_t get_dir_mode(mode_t mode)
 	return mode;
 }
 
-static void sieve_file_storage_get_permissions
-(struct sieve_storage *storage, const char *path,
-mode_t *file_mode_r, mode_t *dir_mode_r, gid_t *gid_r,
-	const char **gid_origin_r)
-{
-	struct stat st;
-
-	/* Use safe defaults */
-	*file_mode_r = 0600;
-	*dir_mode_r = 0700;
-	*gid_r = (gid_t)-1;
-	*gid_origin_r = "defaults";
-
-	if ( stat(path, &st) < 0 ) {
-		if ( !ENOTFOUND(errno) ) {
-			sieve_storage_sys_error(storage,
-				"stat(%s) failed: %m", path);
-		} else {
-			sieve_storage_sys_debug(storage,
-				"Permission lookup failed from %s", path);
-		}
-		return;
-
-	} else {
-		*file_mode_r = (st.st_mode & 0666) | 0600;
-		*dir_mode_r = (st.st_mode & 0777) | 0700;
-		*gid_origin_r = path;
-
-		if ( !S_ISDIR(st.st_mode) ) {
-			/* We're getting permissions from a file. Apply +x modes as necessary. */
-			*dir_mode_r = get_dir_mode(*dir_mode_r);
-		}
-
-		if (S_ISDIR(st.st_mode) && (st.st_mode & S_ISGID) != 0) {
-			/* Directory's GID is used automatically for new files */
-			*gid_r = (gid_t)-1;
-		} else if ((st.st_mode & 0070) >> 3 == (st.st_mode & 0007)) {
-			/* Group has same permissions as world, so don't bother changing it */
-			*gid_r = (gid_t)-1;
-		} else if (getegid() == st.st_gid) {
-			/* Using our own gid, no need to change it */
-			*gid_r = (gid_t)-1;
-		} else {
-			*gid_r = st.st_gid;
-		}
-	}
-
-	sieve_storage_sys_debug(storage,
-		"Using permissions from %s: mode=0%o gid=%ld",
-		path, (int)*dir_mode_r, *gid_r == (gid_t)-1 ? -1L : (long)*gid_r);
-}
-
 static int mkdir_verify
 (struct sieve_storage *storage, const char *dir,
 	mode_t mode, gid_t gid, const char *gid_origin)
@@ -233,34 +222,47 @@ static struct sieve_storage *sieve_file_storage_alloc(void)
 	return &fstorage->storage;
 }
 
-static int sieve_file_storage_init_paths
-(struct sieve_file_storage *fstorage, const char *active_path,
-	const char *storage_path, enum sieve_error *error_r)
-	ATTR_NULL(2, 3)
+static int sieve_file_storage_get_full_path
+(struct sieve_file_storage *fstorage, const char **storage_path,
+	enum sieve_error *error_r)
 {
 	struct sieve_storage *storage = &fstorage->storage;
 	struct sieve_instance *svinst = storage->svinst;
-	const char *tmp_dir, *link_path, *path, *active_fname;
-	mode_t dir_create_mode, file_create_mode;
-	gid_t file_create_gid;
-	const char *file_create_gid_origin;
-	int ret;
+	const char *path = *storage_path;
 
-	fstorage->prev_mtime = (time_t)-1;
+	/* Get full storage path */
 
-	/* Active script path */
+	if ( path != NULL &&
+		((path[0] == '~' && (path[1] == '/' || path[1] == '\0')) ||
+		(((svinst->flags & SIEVE_FLAG_HOME_RELATIVE) != 0 ) && path[0] != '/')) ) {
+		/* home-relative path. change to absolute. */
+		const char *home = sieve_environment_get_homedir(svinst);
 
-	if ( storage->main_storage ||
-		(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
-		if ( active_path == NULL || *active_path == '\0' ) {
-			sieve_storage_sys_debug(storage,
-				"Active script path is unconfigured; "
-				"using default (path=%s)", SIEVE_FILE_DEFAULT_PATH);
-			active_path = SIEVE_FILE_DEFAULT_PATH;
+		if ( home != NULL ) {
+			if ( path[0] == '~' && (path[1] == '/' || path[1] == '\0') )
+				path = home_expand_tilde(path, home);
+			else
+				path = t_strconcat(home, "/", path, NULL);
+		} else {
+			sieve_storage_set_critical(storage,
+				"Sieve storage path `%s' is relative to home directory, "
+				"but home directory is not available.", path);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
 		}
 	}
+	*storage_path = path;
+	return 0;
+}
+
+static int sieve_file_storage_get_full_active_path
+(struct sieve_file_storage *fstorage, const char **active_path,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct sieve_instance *svinst = storage->svinst;
+	const char *path = *active_path;
 
-	path = active_path;
 	if ( path != NULL && *path != '\0' &&
 		((path[0] == '~' && (path[1] == '/' || path[1] == '\0')) ||
 		(((svinst->flags & SIEVE_FLAG_HOME_RELATIVE) != 0 ) && path[0] != '/'))
@@ -275,97 +277,139 @@ static int sieve_file_storage_init_paths
 				path = t_strconcat(home, "/", path, NULL);
 		} else {
 			sieve_storage_set_critical(storage,
-				"Sieve storage path `%s' is relative to home directory, "
+				"Sieve storage active script path `%s' is relative to home directory, "
 				"but home directory is not available.", path);
 			*error_r = SIEVE_ERROR_TEMP_FAILURE;
 			return -1;
 		}
 	}
-	active_path = path;
+	*active_path = path;
+	return 0;
+}
+
+static int sieve_file_storage_init_common
+(struct sieve_file_storage *fstorage, const char *active_path,
+	const char *storage_path, bool exists, enum sieve_error *error_r)
+	ATTR_NULL(2, 3)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	const char *tmp_dir, *link_path, *active_fname;
+	int ret;
+
+	fstorage->prev_mtime = (time_t)-1;
+
+	/* Get active script path */
+
+	if ( sieve_file_storage_get_full_active_path
+		(fstorage, &active_path, error_r) < 0 )
+		return -1;
 
 	/* Get the filename for the active script link */
 
 	active_fname = NULL;
 	if ( active_path != NULL && *active_path != '\0' ) {
 		active_fname = strrchr(active_path, '/');
-		if ( active_fname == NULL )
+		if ( active_fname == NULL ) {
 			active_fname = active_path;
-		else
+		} else {
 			active_fname++;
+		}
 
 		if ( *active_fname == '\0' ) {
 			/* Link cannot be just a path ending in '/' */
 			sieve_storage_set_critical(storage,
-				"Path to active symlink must include the link's filename "
-				"(path=%s)", active_path);
+				"Path to %sscript must include the filename (path=%s)",
+				( storage_path != NULL ? "active link/" : "" ),
+				active_path);
 			*error_r = SIEVE_ERROR_TEMP_FAILURE;
 			return -1;
 		}
+
+		sieve_storage_sys_debug(storage,
+			"Using %sSieve script path: %s",
+			( storage_path != NULL ? "active " : "" ),
+			active_path);
+
+		fstorage->active_path = p_strdup(storage->pool, active_path);
+		fstorage->active_fname = p_strdup(storage->pool, active_fname);
 	}
 
-	/* Storage path */
+	/* Determine storage path */
 
-	path = storage_path;
-	if ( path != NULL &&
-		((path[0] == '~' && (path[1] == '/' || path[1] == '\0')) ||
-		(((svinst->flags & SIEVE_FLAG_HOME_RELATIVE) != 0 ) && path[0] != '/')) ) {
-		/* home-relative path. change to absolute. */
-		const char *home = sieve_environment_get_homedir(svinst);
+	if (storage_path != NULL && *storage_path != '\0') {
+		sieve_storage_sys_debug(storage,
+			"Using script storage path: %s", storage_path);
 
-		if ( home != NULL ) {
-			if ( path[0] == '~' && (path[1] == '/' || path[1] == '\0') )
-				path = home_expand_tilde(path, home);
-			else
-				path = t_strconcat(home, "/", path, NULL);
-		} else {
-			sieve_storage_set_critical(storage,
-				"Sieve storage path `%s' is relative to home directory, "
-				"but home directory is not available.", path);
-			*error_r = SIEVE_ERROR_TEMP_FAILURE;
-			return -1;
+		if ( active_path != NULL && *active_path != '\0' ) {
+			/* Get the path to be prefixed to the script name in the symlink pointing
+			 * to the active script.
+			 */
+			link_path = sieve_storage_get_relative_link_path
+				(fstorage->active_path, storage_path);
+
+			sieve_storage_sys_debug(storage,
+				"Relative path to sieve storage in active link: %s",
+				link_path);
+
+			fstorage->link_path = p_strdup(storage->pool, link_path);
 		}
-	}
-	storage_path = path;
 
-	if (storage_path == NULL || *storage_path == '\0') {
+	} else if ((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
 		sieve_storage_set_critical(storage,
-			"Storage path cannot be empty");
+			"Storage path cannot be empty for write access");
 		*error_r = SIEVE_ERROR_TEMP_FAILURE;
 		return -1;
 	}
-	
-	sieve_storage_sys_debug(storage,
-		"Using script storage path: %s", storage_path);
 
-	fstorage->path = p_strdup(storage->pool, storage_path);
+	fstorage->path = ( storage_path != NULL ?
+		p_strdup(storage->pool, storage_path) : 
+		p_strdup(storage->pool, active_path) );
 
-	if ( active_path != NULL && *active_path != '\0' ) {
-		sieve_storage_sys_debug(storage,
-			"Using active Sieve script path: %s", active_path);
+	/* Prepare for write access */
 
-		fstorage->active_path = p_strdup(storage->pool, active_path);
-		fstorage->active_fname = p_strdup(storage->pool, active_fname);
+	if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+		mode_t dir_create_mode, file_create_mode;
+		gid_t file_create_gid;
+		const char *file_create_gid_origin;
+
+		/* Use safe permission defaults */
+		file_create_mode = 0600;
+		dir_create_mode = 0700;
+		file_create_gid = (gid_t)-1;
+		file_create_gid_origin = "defaults";
+
+		/* Get actual permissions */
+		if ( exists ) {
+			file_create_mode = (fstorage->st.st_mode & 0666) | 0600;
+			dir_create_mode = (fstorage->st.st_mode & 0777) | 0700;
+			file_create_gid_origin = storage_path;
+
+			if ( !S_ISDIR(fstorage->st.st_mode) ) {
+				/* We're getting permissions from a file.
+				   Apply +x modes as necessary. */
+				dir_create_mode = get_dir_mode(dir_create_mode);
+			}
 
-		/* Get the path to be prefixed to the script name in the symlink pointing
-		 * to the active script.
-		 */
-		link_path = sieve_storage_get_relative_link_path
-			(fstorage->active_path, fstorage->path);
+			if (S_ISDIR(fstorage->st.st_mode) &&
+				(fstorage->st.st_mode & S_ISGID) != 0) {
+				/* Directory's GID is used automatically for new files */
+				file_create_gid = (gid_t)-1;
+			} else if ((fstorage->st.st_mode & 0070) >> 3 ==
+				(fstorage->st.st_mode & 0007)) {
+				/* Group has same permissions as world, so don't bother changing it */
+				file_create_gid = (gid_t)-1;
+			} else if (getegid() == fstorage->st.st_gid) {
+				/* Using our own gid, no need to change it */
+				file_create_gid = (gid_t)-1;
+			} else {
+				file_create_gid = fstorage->st.st_gid;
+			}
+		}
 
 		sieve_storage_sys_debug(storage,
-			"Relative path to sieve storage in active link: %s",
-			link_path);
-
-		fstorage->link_path = p_strdup(storage->pool, link_path);
-	}
-
-	/* Prepare for write access */
-
-	if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
-		/* Get permissions */
-		sieve_file_storage_get_permissions(storage,
-			fstorage->path, &file_create_mode, &dir_create_mode, &file_create_gid,
-			&file_create_gid_origin);
+			"Using permissions from %s: mode=0%o gid=%ld",
+			storage_path, (int)dir_create_mode,
+			file_create_gid == (gid_t)-1 ? -1L : (long)file_create_gid);
 
 		/*
 		 * Ensure sieve local directory structure exists (full autocreate):
@@ -392,6 +436,13 @@ static int sieve_file_storage_init_paths
 		fstorage->file_create_gid = file_create_gid;
 	}
 
+	if ( !exists && sieve_file_storage_stat
+		(fstorage, fstorage->path, error_r) < 0 )
+		return -1;
+
+	if (storage->location == NULL)
+		storage->location = p_strdup(storage->pool, storage_path);
+
 	return 0;
 }
 
@@ -401,7 +452,9 @@ static int sieve_file_storage_init
 {
 	struct sieve_file_storage *fstorage =
 		(struct sieve_file_storage *)storage;
+	const char *storage_path = storage->location;
 	const char *active_path = "";
+	bool exists = FALSE;
 
 	if ( options != NULL ) {
 		while ( *options != NULL ) {
@@ -420,68 +473,198 @@ static int sieve_file_storage_init
 		}
 	}
 
-	return sieve_file_storage_init_paths
-		(fstorage, active_path, storage->location, error_r);
+	/* Get full storage path */
+
+	if ( sieve_file_storage_get_full_path
+		(fstorage, &storage_path, error_r) < 0 )
+		return -1;
+
+	/* Stat storage directory */
+
+	if ( storage_path != NULL && *storage_path != '\0' ) {
+		if ( sieve_file_storage_stat (fstorage, storage_path, error_r) < 0 ) {
+			if ( (*error_r != SIEVE_ERROR_NOT_FOUND) ||
+				(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0  )
+				return -1;
+		} else {
+			exists = TRUE;
+
+			if ( !S_ISDIR(fstorage->st.st_mode) ) {
+				if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+					sieve_storage_set_critical(storage,
+						"Sieve storage path `%s' is not a directory, "
+						"but it is to be opened for write access", storage_path);
+					*error_r = SIEVE_ERROR_TEMP_FAILURE;
+					return -1;
+				}
+				if ( active_path != NULL && *active_path != '\0' ) {
+					sieve_storage_sys_warning(storage,
+						"Explicitly specified active script path `%s' is ignored; "
+						"storage path `%s' is not a directory",
+						active_path, storage_path);
+				}
+				active_path = storage_path;
+				storage_path = NULL;
+
+			} 
+		}
+	}
+
+	if ( active_path == NULL || *active_path == '\0' ) {
+		if ( storage->main_storage ||
+			(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0) {
+			sieve_storage_sys_debug(storage,
+				"Active script path is unconfigured; "
+				"using default (path=%s)", SIEVE_FILE_DEFAULT_PATH);
+				active_path = SIEVE_FILE_DEFAULT_PATH;
+		}
+	}
+
+	return sieve_file_storage_init_common
+		(fstorage, active_path, storage_path, exists, error_r);
 }
 
-struct sieve_storage *sieve_file_storage_init_legacy
-(struct sieve_instance *svinst, const char *active_path,
-	const char *storage_path, enum sieve_storage_flags flags,
-	enum sieve_error *error_r)
+static void sieve_file_storage_autodetect
+(struct sieve_file_storage *fstorage, const char **storage_path_r)
 {
-	struct sieve_storage *storage;
-	struct sieve_file_storage *fstorage;
+	struct sieve_storage *storage = &fstorage->storage;
+	struct sieve_instance *svinst = storage->svinst;
+	const char *home = sieve_environment_get_homedir(svinst);
+	int mode = ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ?
+		R_OK|W_OK|X_OK : R_OK|X_OK );
 
-	storage = sieve_storage_alloc
-		(svinst, &sieve_file_storage, "", flags, TRUE);
-	storage->location = p_strdup(storage->pool, storage_path);
-	fstorage = (struct sieve_file_storage *)storage;
-	
-	T_BEGIN {
-		if ( storage_path == NULL || *storage_path == '\0' ) {
-			const char *home = sieve_environment_get_homedir(svinst);
+	sieve_storage_sys_debug(storage,
+		"Performing auto-detection");
 
+	/* We'll need to figure out the storage location ourself.
+	 *
+	 * It's $HOME/sieve or /sieve when (presumed to be) chrooted.
+	 */
+	if ( home != NULL && *home != '\0' ) {
+		if (access(home, mode) == 0) {
+			/* Use default ~/sieve */
 			sieve_storage_sys_debug(storage,
-				"Performing auto-detection");
+				"Root exists (%s)", home);
 
-			/* We'll need to figure out the storage location ourself.
-			 *
-			 * It's $HOME/sieve or /sieve when (presumed to be) chrooted.
-			 */
-			if ( home != NULL && *home != '\0' ) {
-				if (access(home, R_OK|W_OK|X_OK) == 0) {
-					/* Use default ~/sieve */
-					sieve_storage_sys_debug(storage,
-						"Root exists (%s)", home);
-
-					storage_path = t_strconcat(home, "/sieve", NULL);
-				} else {
-					/* Don't have required access on the home directory */
-
-					sieve_storage_sys_debug(storage,
-						"access(%s, rwx) failed: %m", home);
-				}
-			} else {
-					sieve_storage_sys_debug(storage,
-						"HOME is not set");
+			*storage_path_r = t_strconcat(home, "/sieve", NULL);
+		} else {
+			/* Don't have required access on the home directory */
+
+			sieve_storage_sys_debug(storage,
+				"access(%s, rwx) failed: %m", home);
+		}
+	} else {
+			sieve_storage_sys_debug(storage,
+				"HOME is not set");
+
+		if (access("/sieve", mode) == 0) {
+			*storage_path_r = "/sieve";
+			sieve_storage_sys_debug(storage,
+				"Directory `/sieve' exists, assuming chroot");
+		}
+	}
+}
 
-				if (access("/sieve", R_OK|W_OK|X_OK) == 0) {
-					storage_path = "/sieve";
-					sieve_storage_sys_debug(storage,
-						"Directory `/sieve' exists, assuming chroot");
+static int sieve_file_storage_do_init_legacy
+(struct sieve_file_storage *fstorage, const char *active_path,
+	const char *storage_path, enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	bool explicit = FALSE, exists = FALSE;
+
+	if ( storage_path == NULL || *storage_path == '\0' ) {
+		/* Try autodectection */	
+		sieve_file_storage_autodetect(fstorage, &storage_path);
+
+		if ( storage_path != NULL && *storage_path != '\0') {
+			/* Got something: stat it */
+			if (sieve_file_storage_stat
+				(fstorage, storage_path, error_r) < 0 ) {
+				if (*error_r != SIEVE_ERROR_NOT_FOUND) {
+					/* Error */
+					return -1;
 				}
+			} else 	if ( S_ISDIR(fstorage->st.st_mode) ) {
+				/* Success */
+				exists = TRUE;
 			}
 		}
 
-		if (storage_path == NULL || *storage_path == '\0') {
+		if ( (storage_path == NULL || *storage_path == '\0') &&
+			(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
 			sieve_storage_set_critical(storage,
-				"Could not find storage root directory; "
+				"Could not find storage root directory for write access; "
 				"path was left unconfigured and autodetection failed");
 			*error_r = SIEVE_ERROR_TEMP_FAILURE;
-			sieve_storage_unref(&storage);
-			storage = NULL;
-		} else if (sieve_file_storage_init_paths
-			(fstorage, active_path, storage_path, error_r) < 0) {
+			return -1;
+		}
+
+	} else { 
+		/* Get full storage path */
+		if ( sieve_file_storage_get_full_path
+			(fstorage, &storage_path, error_r) < 0 )
+			return -1;
+
+		/* Stat storage directory */
+		if ( sieve_file_storage_stat(fstorage, storage_path, error_r) < 0 ) {
+			if ( (*error_r != SIEVE_ERROR_NOT_FOUND) )
+				return -1;
+			if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 )
+				storage_path = NULL;
+		} else {
+			exists = TRUE;
+		}
+	
+		/* Storage path must be a directory */
+		if ( exists && !S_ISDIR(fstorage->st.st_mode) ) {
+			sieve_storage_set_critical(storage,
+				"Sieve storage path `%s' configured using sieve_dir "
+				"is not a directory", storage_path);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+
+		explicit = TRUE;
+	}
+
+	if ( (active_path == NULL || *active_path == '\0') ) {
+		if ( storage->main_storage ||
+		(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0) {
+			sieve_storage_sys_debug(storage,
+				"Active script path is unconfigured; "
+				"using default (path=%s)", SIEVE_FILE_DEFAULT_PATH);
+			active_path = SIEVE_FILE_DEFAULT_PATH;
+		} else {
+			return -1;
+		}
+	}
+
+	if ( !explicit && !exists &&
+		active_path != NULL && *active_path != '\0' &&
+		(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 )
+		storage_path = NULL;
+	
+	if ( sieve_file_storage_init_common
+		(fstorage, active_path, storage_path, exists, error_r) < 0 )
+		return -1;
+	return 0;
+}
+
+struct sieve_storage *sieve_file_storage_init_legacy
+(struct sieve_instance *svinst, const char *active_path,
+	const char *storage_path, enum sieve_storage_flags flags,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage;
+	struct sieve_file_storage *fstorage;
+
+	storage = sieve_storage_alloc
+		(svinst, &sieve_file_storage, "", flags, TRUE);
+	fstorage = (struct sieve_file_storage *)storage;
+
+	T_BEGIN {
+		if ( sieve_file_storage_do_init_legacy
+			(fstorage, active_path, storage_path, error_r) < 0 ) {
 			sieve_storage_unref(&storage);
 			storage = NULL;
 		}
@@ -497,16 +680,17 @@ struct sieve_file_storage *sieve_file_storage_init_from_path
 	struct sieve_storage *storage;
 	struct sieve_file_storage *fstorage;
 
+	i_assert( path != NULL );
+
 	storage = sieve_storage_alloc
 		(svinst, &sieve_file_storage, "", flags, FALSE);
-	storage->location = p_strdup(storage->pool, path);
 	fstorage = (struct sieve_file_storage *)storage;
 	
 	T_BEGIN {
-		if ( sieve_file_storage_init_paths
-			(fstorage, NULL, path, error_r) < 0 ) {
+		if ( sieve_file_storage_init_common
+			(fstorage, path, NULL, FALSE, error_r) < 0 ) {
 			sieve_storage_unref(&storage);
-			storage = NULL;
+			fstorage = NULL;
 		}
 	} T_END;
 
@@ -520,6 +704,9 @@ static int sieve_file_storage_is_singular
 		(struct sieve_file_storage *)storage;
 	struct stat st;
 
+	if ( fstorage->active_path == NULL )
+		return 1;
+
 	/* Stat the file */
 	if ( lstat(fstorage->active_path, &st) != 0 ) {
 		if ( errno != ENOENT ) {
@@ -542,7 +729,6 @@ static int sieve_file_storage_is_singular
 	return 1;
 }
 
-
 /*
  *
  */
diff --git a/src/lib-sieve/storage/file/sieve-file-storage.h b/src/lib-sieve/storage/file/sieve-file-storage.h
index 1614e30d6..b1501a84c 100644
--- a/src/lib-sieve/storage/file/sieve-file-storage.h
+++ b/src/lib-sieve/storage/file/sieve-file-storage.h
@@ -35,6 +35,9 @@ struct sieve_file_storage {
 	const char *active_fname;
 	const char *link_path;
 
+	struct stat st;
+	struct stat lnk_st;
+
 	mode_t dir_create_mode;
 	mode_t file_create_mode;
 	gid_t file_create_gid;
-- 
GitLab