diff options
Diffstat (limited to 'pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c b/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c new file mode 100644 index 0000000..3c4ec60 --- /dev/null +++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c @@ -0,0 +1,832 @@ +/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "mempool.h" +#include "path-util.h" +#include "istream.h" +#include "time-util.h" +#include "eacces-error.h" + +#include "sieve-binary.h" +#include "sieve-script-private.h" + +#include "sieve-file-storage.h" + +#include <stdio.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> +#include <fcntl.h> + +/* + * Filename to name/name to filename + */ + +const char *sieve_script_file_get_scriptname(const char *filename) +{ + const char *ext; + + /* Extract the script name */ + ext = strrchr(filename, '.'); + if ( ext == NULL || ext == filename || + strcmp(ext, "."SIEVE_SCRIPT_FILEEXT) != 0 ) + return NULL; + + return t_strdup_until(filename, ext); +} + +bool sieve_script_file_has_extension(const char *filename) +{ + return ( sieve_script_file_get_scriptname(filename) != NULL ); +} + +const char *sieve_script_file_from_name(const char *name) +{ + return t_strconcat(name, "."SIEVE_SCRIPT_FILEEXT, NULL); +} + +/* + * Common error handling + */ + +static void sieve_file_script_handle_error +(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; + const char *abspath, *error; + + switch ( errno ) { + case ENOENT: + if (t_abspath(path, &abspath, &error) < 0) { + sieve_script_set_error(script, + SIEVE_ERROR_TEMP_FAILURE, + "t_abspath(%s) failed: %s", + path, error); + *error_r = SIEVE_ERROR_TEMP_FAILURE; + break; + } + e_debug(script->event, "File `%s' not found", abspath); + sieve_script_set_error(script, + SIEVE_ERROR_NOT_FOUND, + "Sieve script `%s' not found", name); + *error_r = SIEVE_ERROR_NOT_FOUND; + break; + case EACCES: + sieve_script_set_critical(script, + "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 %s sieve script: %s(%s) failed: %m", + op, op, path); + *error_r = SIEVE_ERROR_TEMP_FAILURE; + break; + } +} + +/* + * + */ + +static struct sieve_file_script *sieve_file_script_alloc(void) +{ + struct sieve_file_script *fscript; + pool_t pool; + + pool = pool_alloconly_create("sieve_file_script", 2048); + fscript = p_new(pool, struct sieve_file_script, 1); + fscript->script = sieve_file_script; + fscript->script.pool = pool; + + return fscript; +} + +struct sieve_file_script *sieve_file_script_init_from_filename +(struct sieve_file_storage *fstorage, const char *filename, + const char *scriptname) +{ + struct sieve_storage *storage = &fstorage->storage; + struct sieve_file_script *fscript = NULL; + + /* Prevent initializing the active script link as a script when it + * resides in the sieve storage directory. + */ + if ( scriptname != NULL && fstorage->link_path != NULL && + *(fstorage->link_path) == '\0' ) { + if ( strcmp(filename, fstorage->active_fname) == 0 ) { + sieve_storage_set_error(storage, + SIEVE_ERROR_NOT_FOUND, + "Script `%s' does not exist.", scriptname); + return NULL; + } + } + + fscript = sieve_file_script_alloc(); + sieve_script_init + (&fscript->script, storage, &sieve_file_script, + sieve_file_storage_path_extend(fstorage, filename), scriptname); + fscript->filename = p_strdup(fscript->script.pool, filename); + return fscript; +} + +struct sieve_file_script *sieve_file_script_open_from_filename +(struct sieve_file_storage *fstorage, const char *filename, + const char *scriptname) +{ + struct sieve_file_script *fscript; + enum sieve_error error; + + fscript = sieve_file_script_init_from_filename + (fstorage, filename, scriptname); + if ( fscript == NULL ) + return NULL; + + if ( sieve_script_open(&fscript->script, &error) < 0 ) { + struct sieve_script *script = &fscript->script; + sieve_script_unref(&script); + return NULL; + } + + return fscript; +} + +struct sieve_file_script *sieve_file_script_init_from_name +(struct sieve_file_storage *fstorage, const char *name) +{ + struct sieve_storage *storage = &fstorage->storage; + struct sieve_file_script *fscript; + + 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->active_path, name); + return fscript; +} + +struct sieve_file_script *sieve_file_script_open_from_name +(struct sieve_file_storage *fstorage, const char *name) +{ + struct sieve_file_script *fscript; + enum sieve_error error; + + fscript = sieve_file_script_init_from_name(fstorage, name); + if ( fscript == NULL ) + return NULL; + + if ( sieve_script_open(&fscript->script, &error) < 0 ) { + struct sieve_script *script = &fscript->script; + sieve_script_unref(&script); + return NULL; + } + + return fscript; +} + +struct sieve_file_script *sieve_file_script_init_from_path +(struct sieve_file_storage *fstorage, const char *path, + const char *scriptname, enum sieve_error *error_r) +{ + struct sieve_instance *svinst = fstorage->storage.svinst; + struct sieve_file_storage *fsubstorage; + struct sieve_file_script *fscript; + struct sieve_storage *substorage; + enum sieve_error error; + + if ( error_r != NULL ) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + fsubstorage = sieve_file_storage_init_from_path + (svinst, path, 0, error_r); + if (fsubstorage == NULL) + return NULL; + substorage = &fsubstorage->storage; + + fscript = sieve_file_script_alloc(); + sieve_script_init(&fscript->script, + substorage, &sieve_file_script, path, scriptname); + sieve_storage_unref(&substorage); + + return fscript; +} + +struct sieve_file_script *sieve_file_script_open_from_path +(struct sieve_file_storage *fstorage, const char *path, + const char *scriptname, enum sieve_error *error_r) +{ + struct sieve_storage *storage = &fstorage->storage; + struct sieve_file_script *fscript; + enum sieve_error error; + + if ( error_r != NULL ) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + fscript = sieve_file_script_init_from_path + (fstorage, path, scriptname, error_r); + if (fscript == NULL) { + sieve_storage_set_error(storage, + *error_r, "Failed to open script"); + return NULL; + } + + if ( sieve_script_open(&fscript->script, error_r) < 0 ) { + struct sieve_script *script = &fscript->script; + const char *errormsg; + + errormsg = sieve_script_get_last_error(&fscript->script, error_r); + sieve_storage_set_error(storage, + *error_r, "%s", errormsg); + sieve_script_unref(&script); + return NULL; + } + + return fscript; +} + +/* + * Open + */ + +static int sieve_file_script_stat +(const char *path, struct stat *st, struct stat *lnk_st) +{ + if ( lstat(path, st) < 0 ) + return -1; + + *lnk_st = *st; + + if ( S_ISLNK(st->st_mode) && stat(path, st) < 0 ) + return -1; + + return 0; +} + +static const char * +path_split_filename(const char *path, const char **dirpath_r) +{ + const char *filename; + + filename = strrchr(path, '/'); + if ( filename == NULL ) { + *dirpath_r = ""; + filename = path; + } else { + *dirpath_r = t_strdup_until(path, filename); + filename++; + } + return filename; +} + +static int sieve_file_script_open +(struct sieve_script *script, enum sieve_error *error_r) +{ + struct sieve_file_script *fscript = + (struct sieve_file_script *)script; + struct sieve_storage *storage = script->storage; + struct sieve_file_storage *fstorage = + (struct sieve_file_storage *)storage; + pool_t pool = script->pool; + const char *filename, *name, *path; + const char *dirpath, *basename, *binpath, *binprefix; + struct stat st, lnk_st; + bool success = TRUE; + int ret = 0; + + filename = fscript->filename; + basename = NULL; + name = script->name; + st = fstorage->st; + lnk_st = fstorage->lnk_st; + + if (name == NULL) + name = storage->script_name; + + T_BEGIN { + 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; + } + dirpath = path; + + path = sieve_file_storage_path_extend(fstorage, filename); + ret = sieve_file_script_stat(path, &st, &lnk_st); + } + + } else { + /* Storage is a single file */ + path = fstorage->active_path; + + /* Extract filename from path */ + filename = path_split_filename(path, &dirpath); + + if ( (basename=sieve_script_file_get_scriptname(filename)) == NULL ) + basename = filename; + + if ( name == NULL ) + name = basename; + } + + if ( success ) { + if ( ret < 0 ) { + /* Make sure we have a script name for the error */ + if ( name == NULL ) { + i_assert( basename != NULL ); + name = basename; + } + sieve_file_script_handle_error + (fscript, "stat", path, name, error_r); + success = FALSE; + + } else if ( !S_ISREG(st.st_mode) ) { + sieve_script_set_critical(script, + "Sieve script file '%s' is not a regular file.", path); + *error_r = SIEVE_ERROR_TEMP_FAILURE; + success = FALSE; + } + } + + if ( success ) { + const char *bpath, *bfile, *bprefix; + + if ( storage->bin_dir != NULL ) { + bpath = storage->bin_dir; + bfile = sieve_binfile_from_name(name); + bprefix = name; + + } else { + bpath = dirpath; + bfile = sieve_binfile_from_name(basename); + bprefix = basename; + } + + if ( *bpath == '\0' ) { + binpath = bfile; + binprefix = bprefix; + } else if ( bpath[strlen(bpath)-1] == '/' ) { + binpath = t_strconcat(bpath, bfile, NULL); + binprefix = t_strconcat(bpath, bprefix, NULL); + } else { + binpath = t_strconcat(bpath, "/", bfile, NULL); + binprefix = t_strconcat(bpath, "/", bprefix, NULL); + } + + fscript->st = st; + fscript->lnk_st = lnk_st; + fscript->path = p_strdup(pool, path); + fscript->filename = p_strdup(pool, filename); + fscript->dirpath = p_strdup(pool, dirpath); + fscript->binpath = p_strdup(pool, binpath); + fscript->binprefix = p_strdup(pool, binprefix); + + fscript->script.location = fscript->path; + + if ( fscript->script.name == NULL ) + fscript->script.name = p_strdup(pool, basename); + } + } T_END; + + return ( success ? 0 : -1 ); +} + +static int sieve_file_script_get_stream +(struct sieve_script *script, struct istream **stream_r, + enum sieve_error *error_r) +{ + struct sieve_file_script *fscript = (struct sieve_file_script *)script; + struct stat st; + struct istream *result; + int fd; + + if ( (fd=open(fscript->path, O_RDONLY)) < 0 ) { + sieve_file_script_handle_error + (fscript, "open", fscript->path, fscript->script.name, error_r); + return -1; + } + + if ( fstat(fd, &st) != 0 ) { + sieve_script_set_critical(script, + "Failed to open sieve script: fstat(fd=%s) failed: %m", + fscript->path); + *error_r = SIEVE_ERROR_TEMP_FAILURE; + result = NULL; + } else { + /* Re-check the file type just to be sure */ + if ( !S_ISREG(st.st_mode) ) { + sieve_script_set_critical(script, + "Sieve script file `%s' is not a regular file", fscript->path); + *error_r = SIEVE_ERROR_TEMP_FAILURE; + result = NULL; + } else { + result = i_stream_create_fd_autoclose(&fd, SIEVE_FILE_READ_BLOCK_SIZE); + fscript->st = fscript->lnk_st = st; + } + } + + if ( result == NULL ) { + /* Something went wrong, close the fd */ + if ( fd >= 0 && close(fd) != 0 ) { + e_error(script->event, + "Failed to close sieve script: " + "close(fd=%s) failed: %m", fscript->path); + } + return -1; + } + + *stream_r = result; + return 0; +} + +/* + * Binary + */ + +static int sieve_file_script_binary_read_metadata +(struct sieve_script *script, struct sieve_binary_block *sblock, + sieve_size_t *offset ATTR_UNUSED) +{ + struct sieve_file_script *fscript = (struct sieve_file_script *)script; + struct sieve_instance *svinst = script->storage->svinst; + struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock); + const struct stat *sstat, *bstat; + + bstat = sieve_binary_stat(sbin); + if ( fscript->st.st_mtime > fscript->lnk_st.st_mtime || + (fscript->st.st_mtime == fscript->lnk_st.st_mtime && + ST_MTIME_NSEC(fscript->st) >= ST_MTIME_NSEC(fscript->lnk_st)) ) { + sstat = &fscript->st; + } else { + sstat = &fscript->lnk_st; + } + + if ( bstat->st_mtime < sstat->st_mtime || + (bstat->st_mtime == sstat->st_mtime && + ST_MTIME_NSEC(*bstat) <= ST_MTIME_NSEC(*sstat)) ) { + if ( svinst->debug ) { + e_debug(script->event, + "Sieve binary `%s' is not newer " + "than the Sieve script `%s' (%s.%lu <= %s.%lu)", + sieve_binary_path(sbin), sieve_script_location(script), + t_strflocaltime("%Y-%m-%d %H:%M:%S", bstat->st_mtime), + ST_MTIME_NSEC(*bstat), + t_strflocaltime("%Y-%m-%d %H:%M:%S", sstat->st_mtime), + ST_MTIME_NSEC(*sstat)); + } + return 0; + } + + return 1; +} + +static struct sieve_binary *sieve_file_script_binary_load +(struct sieve_script *script, enum sieve_error *error_r) +{ + struct sieve_file_script *fscript = (struct sieve_file_script *)script; + struct sieve_instance *svinst = script->storage->svinst; + + return sieve_binary_open(svinst, fscript->binpath, script, error_r); +} + +static int sieve_file_script_binary_save +(struct sieve_script *script, struct sieve_binary *sbin, bool update, + enum sieve_error *error_r) +{ + struct sieve_storage *storage = script->storage; + struct sieve_file_script *fscript = (struct sieve_file_script *)script; + + if ( storage->bin_dir != NULL && + sieve_storage_setup_bindir(storage, 0700) < 0 ) + return -1; + + return sieve_binary_save(sbin, fscript->binpath, update, + fscript->st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), error_r); +} + +static const char *sieve_file_script_binary_get_prefix +(struct sieve_script *script) +{ + struct sieve_file_script *fscript = (struct sieve_file_script *)script; + + return fscript->binprefix; +} + +/* + * Management + */ + +static int sieve_file_storage_script_is_active(struct sieve_script *script) +{ + struct sieve_file_script *fscript = + (struct sieve_file_script *) script; + struct sieve_file_storage *fstorage = + (struct sieve_file_storage *)script->storage; + const char *afile; + int ret = 0; + + T_BEGIN { + ret = sieve_file_storage_active_script_get_file(fstorage, &afile); + + if ( ret > 0 ) { + /* Is the requested script active? */ + ret = ( strcmp(fscript->filename, afile) == 0 ? 1 : 0 ); + } + } T_END; + + return ret; +} + +static int sieve_file_storage_script_delete(struct sieve_script *script) +{ + struct sieve_file_script *fscript = + (struct sieve_file_script *)script; + int ret = 0; + + if ( sieve_file_storage_pre_modify(script->storage) < 0 ) + return -1; + + ret = unlink(fscript->path); + if ( ret < 0 ) { + if ( errno == ENOENT ) { + sieve_script_set_error(script, + SIEVE_ERROR_NOT_FOUND, + "Sieve script does not exist."); + } else { + sieve_script_set_critical(script, + "Performing unlink() failed on sieve file `%s': %m", + fscript->path); + } + } + return ret; +} + +static int _sieve_file_storage_script_activate +(struct sieve_file_script *fscript) +{ + struct sieve_script *script = &fscript->script; + struct sieve_storage *storage = script->storage; + struct sieve_file_storage *fstorage = + (struct sieve_file_storage *)storage; + struct stat st; + const char *link_path, *afile; + int activated = 0; + int ret; + + /* Find out whether there is an active script, but recreate + * the symlink either way. This way, any possible error in the symlink + * resolves automatically. This step is only necessary to provide a + * proper return value indicating whether the script was already active. + */ + ret = sieve_file_storage_active_script_get_file(fstorage, &afile); + + /* Is the requested script already active? */ + 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, + "Failed to activate Sieve script: lstat(%s) failed: %m.", + fscript->path); + return -1; + } + + /* Rescue a possible .dovecot.sieve regular file remaining from old + * installations. + */ + if ( !sieve_file_storage_active_rescue_regular(fstorage) ) { + /* Rescue failed, manual intervention is necessary */ + return -1; + } + + /* Just try to create the symlink first */ + link_path = t_strconcat + ( fstorage->link_path, fscript->filename, NULL ); + + ret = symlink(link_path, fstorage->active_path); + if ( ret < 0 ) { + if ( errno == EEXIST ) { + ret = sieve_file_storage_active_replace_link(fstorage, link_path); + if ( ret < 0 ) { + return ret; + } + } else { + /* Other error, critical */ + sieve_script_set_critical(script, + "Failed to activate Sieve script: " + "symlink(%s, %s) failed: %m", + link_path, fstorage->active_path); + return -1; + } + } + return activated; +} + +static int sieve_file_storage_script_activate +(struct sieve_script *script) +{ + struct sieve_file_script *fscript = + (struct sieve_file_script *)script; + int ret; + + if ( sieve_file_storage_pre_modify(script->storage) < 0 ) + return -1; + + T_BEGIN { + ret = _sieve_file_storage_script_activate(fscript); + } T_END; + + return ret; +} + +static int sieve_file_storage_script_rename +(struct sieve_script *script, const char *newname) +{ + struct sieve_file_script *fscript = + (struct sieve_file_script *)script; + struct sieve_storage *storage = script->storage; + struct sieve_file_storage *fstorage = + (struct sieve_file_storage *)storage; + const char *newpath, *newfile, *link_path; + int ret = 0; + + if ( sieve_file_storage_pre_modify(storage) < 0 ) + return -1; + + T_BEGIN { + newfile = sieve_script_file_from_name(newname); + newpath = t_strconcat( fstorage->path, "/", newfile, NULL ); + + /* The normal rename() system call overwrites the existing file without + * notice. Also, active scripts must not be disrupted by renaming a script. + * That is why we use a link(newpath) [activate newpath] unlink(oldpath) + */ + + /* Link to the new path */ + ret = link(fscript->path, newpath); + if ( ret >= 0 ) { + /* Is the requested script active? */ + if ( sieve_script_is_active(script) > 0 ) { + /* Active; make active link point to the new copy */ + i_assert( fstorage->link_path != NULL ); + link_path = t_strconcat + ( fstorage->link_path, newfile, NULL ); + + ret = sieve_file_storage_active_replace_link(fstorage, link_path); + } + + if ( ret >= 0 ) { + /* If all is good, remove the old link */ + if ( unlink(fscript->path) < 0 ) { + e_error(script->event, + "Failed to clean up after rename: " + "unlink(%s) failed: %m", fscript->path); + } + + if ( script->name != NULL && *script->name != '\0' ) + script->name = p_strdup(script->pool, newname); + fscript->path = p_strdup(script->pool, newpath); + fscript->filename = p_strdup(script->pool, newfile); + } else { + /* If something went wrong, remove the new link to restore previous + * state + */ + if ( unlink(newpath) < 0 ) { + e_error(script->event, + "Failed to clean up after failed rename: " + "unlink(%s) failed: %m", newpath); + } + } + } else { + /* Our efforts failed right away */ + switch ( errno ) { + case ENOENT: + sieve_script_set_error(script, SIEVE_ERROR_NOT_FOUND, + "Sieve script does not exist."); + break; + case EEXIST: + sieve_script_set_error(script, SIEVE_ERROR_EXISTS, + "A sieve script with that name already exists."); + break; + default: + sieve_script_set_critical(script, + "Failed to rename Sieve script: " + "link(%s, %s) failed: %m", fscript->path, newpath); + } + } + } T_END; + + return ret; +} + +/* + * Properties + */ + +static int sieve_file_script_get_size +(const struct sieve_script *script, uoff_t *size_r) +{ + struct sieve_file_script *fscript = (struct sieve_file_script *)script; + + *size_r = fscript->st.st_size; + return 1; +} + +const char *sieve_file_script_get_dirpath +(const struct sieve_script *script) +{ + struct sieve_file_script *fscript = (struct sieve_file_script *)script; + + if ( script->driver_name != sieve_file_script.driver_name ) + return NULL; + + return fscript->dirpath; +} + +const char *sieve_file_script_get_path +(const struct sieve_script *script) +{ + struct sieve_file_script *fscript = (struct sieve_file_script *)script; + + if ( script->driver_name != sieve_file_script.driver_name ) + return NULL; + + return fscript->path; +} + +/* + * Matching + */ + +static bool sieve_file_script_equals +(const struct sieve_script *script, const struct sieve_script *other) +{ + struct sieve_file_script *fscript = + (struct sieve_file_script *)script; + struct sieve_file_script *fother = + (struct sieve_file_script *)other; + + return ( CMP_DEV_T(fscript->st.st_dev, fother->st.st_dev) && + fscript->st.st_ino == fother->st.st_ino ); +} + +/* + * Driver definition + */ + +const struct sieve_script sieve_file_script = { + .driver_name = SIEVE_FILE_STORAGE_DRIVER_NAME, + .v = { + .open = sieve_file_script_open, + + .get_stream = sieve_file_script_get_stream, + + .binary_read_metadata = sieve_file_script_binary_read_metadata, + .binary_load = sieve_file_script_binary_load, + .binary_save = sieve_file_script_binary_save, + .binary_get_prefix = sieve_file_script_binary_get_prefix, + + .rename = sieve_file_storage_script_rename, + .delete = sieve_file_storage_script_delete, + .is_active = sieve_file_storage_script_is_active, + .activate = sieve_file_storage_script_activate, + + .get_size = sieve_file_script_get_size, + + .equals = sieve_file_script_equals + } +}; + |