sieve-storage-script.c [plain text]
#include "lib.h"
#include "mempool.h"
#include "hostpid.h"
#include "ioloop.h"
#include "istream.h"
#include "file-copy.h"
#include "eacces-error.h"
#include "sieve-script-private.h"
#include "sieve-storage.h"
#include "sieve-storage-private.h"
#include "sieve-storage-script.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <ctype.h>
#include <time.h>
#include <fcntl.h>
struct sieve_storage_script {
struct sieve_script script;
struct sieve_storage *storage;
};
struct sieve_script *sieve_storage_script_init_from_path
(struct sieve_storage *storage, const char *path,
const char *scriptname)
{
pool_t pool;
struct sieve_storage_script *st_script = NULL;
enum sieve_error error;
if ( *(storage->link_path) == '\0' ) {
const char *fname;
fname = strrchr(path, '/');
if ( fname == NULL )
fname = path;
else
fname++;
if ( strcmp(fname, storage->active_fname) == 0 ) {
sieve_storage_set_error
(storage, SIEVE_ERROR_NOT_FOUND, "Script does not exist.");
return NULL;
}
}
pool = pool_alloconly_create("sieve_storage_script", 4096);
st_script = p_new(pool, struct sieve_storage_script, 1);
st_script->script.pool = pool;
st_script->storage = storage;
if ( sieve_script_init(&st_script->script, storage->svinst, path, scriptname,
sieve_storage_get_error_handler(storage), &error) != NULL ) {
return &st_script->script;
}
pool_unref(&pool);
if ( error == SIEVE_ERROR_NOT_FOUND )
sieve_storage_set_error(storage, error, "Script does not exist.");
return NULL;
}
struct sieve_script *sieve_storage_script_init
(struct sieve_storage *storage, const char *scriptname)
{
struct sieve_script *script;
const char *path;
if ( !sieve_script_name_is_valid(scriptname) ) {
sieve_storage_set_error(storage, SIEVE_ERROR_BAD_PARAMS,
"Invalid script name '%s'.", scriptname);
return NULL;
}
T_BEGIN {
path = t_strconcat( storage->dir, "/", scriptname, ".sieve", NULL );
script = sieve_storage_script_init_from_path(storage, path, NULL);
} T_END;
return script;
}
static struct sieve_script *sieve_storage_script_init_from_file
(struct sieve_storage *storage, const char *scriptfile)
{
struct sieve_script *script;
const char *path;
T_BEGIN {
path = t_strconcat( storage->dir, "/", scriptfile, NULL );
script = sieve_storage_script_init_from_path(storage, path, NULL);
} T_END;
return script;
}
const char *sieve_storage_file_get_scriptname
(const struct sieve_storage *storage ATTR_UNUSED, const char *filename)
{
const char *ext;
ext = strrchr(filename, '.');
if ( ext == NULL || ext == filename || strcmp(ext,".sieve") != 0 )
return NULL;
return t_strdup_until(filename, ext);
}
static int sieve_storage_read_active_link
(struct sieve_storage *storage, const char **link_r)
{
char linkbuf[PATH_MAX];
int ret;
*link_r = NULL;
ret = readlink(storage->active_path, linkbuf, sizeof(linkbuf));
if ( ret < 0 ) {
if (errno == EINVAL) {
i_warning
("sieve-storage: Active sieve script symlink %s is no symlink.",
storage->active_path);
return 0;
}
if (errno == ENOENT ) {
return 0;
}
sieve_storage_set_critical(storage,
"Performing readlink() on active sieve symlink '%s' failed: %m",
storage->active_path);
return -1;
}
*link_r = t_strndup(linkbuf, ret);
return 1;
}
static const char *sieve_storage_parse_link
(struct sieve_storage *storage, const char *link)
{
const char *fname, *scriptname, *scriptpath;
fname = strrchr(link, '/');
if ( fname != NULL ) {
scriptpath = t_strdup_until(link, fname+1);
fname++;
} else {
scriptpath = "";
fname = link;
}
scriptname = sieve_storage_file_get_scriptname(storage, fname);
if ( scriptname == NULL ) {
i_warning
("sieve-storage: Active sieve script symlink %s is broken: "
"invalid scriptname (points to %s).",
storage->active_path, link);
return NULL;
}
if ( strcmp(scriptpath, storage->link_path) != 0 &&
strcmp(scriptpath, storage->dir) != 0 ) {
i_warning
("sieve-storage: Active sieve script symlink %s is broken: "
"invalid/unknown path to storage (points to %s).",
storage->active_path, link);
return NULL;
}
return fname;
}
int sieve_storage_get_active_scriptfile
(struct sieve_storage *storage, const char **file_r)
{
const char *link, *scriptfile;
int ret;
*file_r = NULL;
if ( (ret=sieve_storage_read_active_link(storage, &link)) <= 0 )
return ret;
scriptfile = sieve_storage_parse_link(storage, link);
if (scriptfile == NULL) {
return 0;
}
*file_r = scriptfile;
return 1;
}
struct sieve_script *sieve_storage_get_active_script
(struct sieve_storage *storage)
{
struct sieve_script *script;
const char *scriptfile, *link;
int ret;
sieve_storage_clear_error(storage);
if ( (ret=sieve_storage_read_active_link(storage, &link)) <= 0 ) {
if ( ret == 0 ) {
return sieve_storage_script_init_from_path
(storage, storage->active_path, NULL);
}
return NULL;
}
scriptfile = sieve_storage_parse_link(storage, link);
if (scriptfile == NULL) {
return NULL;
}
script = sieve_storage_script_init_from_file(storage, scriptfile);
if ( script == NULL && storage->error_code == SIEVE_ERROR_NOT_FOUND ) {
i_warning
("sieve-storage: Active sieve script symlink %s "
"points to non-existent script (points to %s).",
storage->active_path, link);
}
return script;
}
int sieve_storage_script_is_active(struct sieve_script *script)
{
struct sieve_storage_script *st_script =
(struct sieve_storage_script *) script;
const char *afile;
int ret = 0;
T_BEGIN {
ret = sieve_storage_get_active_scriptfile(st_script->storage, &afile);
if ( ret > 0 ) {
ret = ( strcmp(script->filename, afile) == 0 ? 1 : 0 );
}
} T_END;
return ret;
}
int sieve_storage_script_delete(struct sieve_script **script)
{
struct sieve_storage_script *st_script =
(struct sieve_storage_script *) *script;
struct sieve_storage *storage = st_script->storage;
int ret = 0;
if ( sieve_storage_script_is_active(*script) ) {
sieve_storage_set_error(storage, SIEVE_ERROR_ACTIVE,
"Cannot delete the active sieve script.");
ret = -1;
} else {
ret = unlink((*script)->path);
if ( ret < 0 ) {
if ( errno == ENOENT )
sieve_storage_set_error(storage, SIEVE_ERROR_NOT_FOUND,
"Sieve script does not exist.");
else
sieve_storage_set_critical(
storage, "Performing unlink() failed on sieve file '%s': %m",
(*script)->path);
}
}
sieve_script_unref(script);
return ret;
}
static bool sieve_storage_rescue_regular_file(struct sieve_storage *storage)
{
struct stat st;
if ( lstat(storage->active_path, &st) != 0 ) {
if ( errno != ENOENT ) {
sieve_storage_set_critical(storage,
"Failed to stat active sieve script symlink (%s): %m.",
storage->active_path);
return FALSE;
}
return TRUE;
}
if ( S_ISLNK( st.st_mode ) ) {
if ( storage->debug )
i_info( "sieve-storage: nothing to rescue %s.", storage->active_path);
return TRUE;
}
if ( S_ISREG( st.st_mode ) ) {
const char *dstpath;
bool result = TRUE;
T_BEGIN {
dstpath = t_strconcat
( storage->dir, "/dovecot.orig.sieve", NULL );
if ( file_copy(storage->active_path, dstpath, 1) < 1 ) {
sieve_storage_set_critical(storage,
"Active sieve script file '%s' is a regular file and copying it to "
"the script storage as '%s' failed. This needs to be fixed manually.",
storage->active_path, dstpath);
result = FALSE;
} else {
i_info("sieve-storage: Moved active sieve script file '%s' "
"to script storage as '%s'.",
storage->active_path, dstpath);
}
} T_END;
return result;
}
sieve_storage_set_critical( storage,
"Active sieve script file '%s' is no symlink nor a regular file. "
"This needs to be fixed manually.", storage->active_path );
return FALSE;
}
int sieve_storage_deactivate(struct sieve_storage *storage)
{
int ret;
if ( !sieve_storage_rescue_regular_file(storage) )
return -1;
ret = unlink(storage->active_path);
if ( ret < 0 ) {
if ( errno != ENOENT ) {
sieve_storage_set_critical(storage, "sieve_storage_deactivate(): "
"error on unlink(%s): %m", storage->active_path);
return -1;
} else
return 0;
}
return 1;
}
static int sieve_storage_replace_active_link
(struct sieve_storage *storage, const char *link_path)
{
const char *active_path_new;
struct timeval *tv, tv_now;
int ret = 0;
tv = &ioloop_timeval;
for (;;) {
active_path_new = t_strdup_printf
("%s-new.%s.P%sM%s.%s.sieve",
storage->active_path,
dec2str(tv->tv_sec), my_pid,
dec2str(tv->tv_usec), my_hostname);
ret = symlink(link_path, active_path_new);
if ( ret < 0 ) {
if ( errno == EEXIST ) {
sleep(2);
tv = &tv_now;
if (gettimeofday(&tv_now, NULL) < 0)
i_fatal("gettimeofday(): %m");
continue;
}
sieve_storage_set_critical
(storage, "Creating symlink() %s to %s failed: %m",
active_path_new, link_path);
return -1;
}
break;
}
ret = rename(active_path_new, storage->active_path);
if ( ret < 0 ) {
(void)unlink(active_path_new);
sieve_storage_set_critical
(storage, "Performing rename() %s to %s failed: %m",
active_path_new, storage->active_path);
return -1;
}
return 1;
}
static int _sieve_storage_script_activate(struct sieve_script *script)
{
struct sieve_storage_script *st_script =
(struct sieve_storage_script *) script;
struct sieve_storage *storage = st_script->storage;
struct stat st;
const char *link_path, *afile;
int activated = 0;
int ret;
ret = sieve_storage_get_active_scriptfile(storage, &afile);
if ( ret <= 0 || strcmp(script->filename, afile) != 0 )
activated = 1;
if ( lstat(script->path, &st) != 0 ) {
sieve_storage_set_critical(storage,
"Stat on sieve script %s failed, but it is to be activated: %m.",
script->path);
return -1;
}
if ( !sieve_storage_rescue_regular_file(storage) ) {
return -1;
}
link_path = t_strconcat
( storage->link_path, script->filename, NULL );
ret = symlink(link_path, storage->active_path);
if ( ret < 0 ) {
if ( errno == EEXIST ) {
ret = sieve_storage_replace_active_link(storage, link_path);
if ( ret < 0 ) {
return ret;
}
} else {
sieve_storage_set_critical
(storage,
"Creating symlink() %s to %s failed: %m",
storage->active_path, link_path);
return -1;
}
}
return activated;
}
int sieve_storage_script_activate(struct sieve_script *script)
{
int ret;
T_BEGIN {
ret = _sieve_storage_script_activate(script);
} T_END;
return ret;
}
int sieve_storage_script_rename
(struct sieve_script *script, const char *newname)
{
struct sieve_storage_script *st_script =
(struct sieve_storage_script *) script;
struct sieve_storage *storage = st_script->storage;
const char *newpath, *newfile, *link_path;
int ret = 0;
if ( !sieve_script_name_is_valid(newname) ) {
sieve_storage_set_error(storage,
SIEVE_ERROR_BAD_PARAMS,
"Invalid new script name '%s'.", newname);
return -1;
}
T_BEGIN {
newfile = t_strconcat( newname, ".sieve", NULL );
newpath = t_strconcat( storage->dir, "/", newfile, NULL );
ret = link(script->path, newpath);
if ( ret >= 0 ) {
if ( sieve_storage_script_is_active(script) ) {
link_path = t_strconcat
( storage->link_path, newfile, NULL );
ret = sieve_storage_replace_active_link(storage, link_path);
}
if ( ret >= 0 ) {
if ( unlink(script->path) < 0 ) {
i_error("Failed to clean up old file link '%s' after rename: %m",
script->path);
}
if ( script->name != NULL && *script->name != '\0' )
script->name = p_strdup(script->pool, newname);
script->path = p_strdup(script->pool, newpath);
script->filename = p_strdup(script->pool, newfile);
script->basename = p_strdup(script->pool, newname);
} else {
if ( unlink(newpath) < 0 ) {
i_error("Failed to clean up new file link '%s'"
" after failed rename: %m", newpath);
}
}
} else {
switch ( errno ) {
case ENOENT:
sieve_storage_set_error(storage, SIEVE_ERROR_NOT_FOUND,
"Sieve script does not exist.");
break;
case EEXIST:
sieve_storage_set_error(storage, SIEVE_ERROR_EXISTS,
"A sieve script with that name already exists.");
break;
default:
sieve_storage_set_critical(
storage, "Performing link(%s, %s) failed: %m",
script->path, newpath);
}
}
} T_END;
return ret;
}