#include "lib.h"
#include "home-expand.h"
#include "ioloop.h"
#include "mkdir-parents.h"
#include "eacces-error.h"
#include "unlink-old-files.h"
#include "mail-storage-private.h"
#include "sieve.h"
#include "sieve-common.h"
#include "sieve-settings.h"
#include "sieve-error-private.h"
#include "sieve-settings.h"
#include "sieve-storage-private.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <ctype.h>
#include <time.h>
#include <utime.h>
#define SIEVE_DEFAULT_PATH "~/.dovecot."SIEVE_SCRIPT_FILEEXT
#define MAX_DIR_CREATE_MODE 0770
#define CRITICAL_MSG \
"Internal error occured. Refer to server log for more information."
#define CRITICAL_MSG_STAMP CRITICAL_MSG " [%Y-%m-%d %H:%M:%S]"
static void sieve_storage_verror
(struct sieve_error_handler *ehandler ATTR_UNUSED,
unsigned int flags ATTR_UNUSED, const char *location ATTR_UNUSED,
const char *fmt, va_list args);
static const char *sieve_storage_get_relative_link_path
(const char *active_path, const char *storage_dir)
{
const char *link_path, *p;
size_t pathlen;
p = strrchr(active_path, '/');
if ( p == NULL ) {
link_path = storage_dir;
} else {
pathlen = p - active_path;
if ( strncmp( active_path, storage_dir, pathlen ) == 0 &&
(storage_dir[pathlen] == '/' || storage_dir[pathlen] == '\0') )
{
if ( storage_dir[pathlen] == '\0' )
link_path = "";
else
link_path = storage_dir + pathlen + 1;
} else
link_path = storage_dir;
}
pathlen = strlen(link_path);
if ( pathlen != 0 && link_path[pathlen-1] != '/')
return t_strconcat(link_path, "/", NULL);
return t_strdup(link_path);
}
static mode_t get_dir_mode(mode_t mode)
{
if ((mode & 0600) != 0) mode |= 0100;
if ((mode & 0060) != 0) mode |= 0010;
if ((mode & 0006) != 0) mode |= 0001;
return mode;
}
static void sieve_storage_get_permissions
(const char *path, mode_t *file_mode_r, mode_t *dir_mode_r, gid_t *gid_r,
const char **gid_origin_r, bool debug)
{
struct stat st;
*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) ) {
i_error("sieve-storage: stat(%s) failed: %m", path);
} else if ( debug ) {
i_debug("sieve-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) ) {
*dir_mode_r = get_dir_mode(*dir_mode_r);
}
if (S_ISDIR(st.st_mode) && (st.st_mode & S_ISGID) != 0) {
*gid_r = (gid_t)-1;
} else if ((st.st_mode & 0070) >> 3 == (st.st_mode & 0007)) {
*gid_r = (gid_t)-1;
} else if (getegid() == st.st_gid) {
*gid_r = (gid_t)-1;
} else {
*gid_r = st.st_gid;
}
}
if ( debug ) {
i_debug("sieve-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
(const char *dir, mode_t mode, gid_t gid, const char *gid_origin, bool debug)
{
struct stat st;
if ( stat(dir, &st) == 0 )
return 0;
if ( errno == EACCES ) {
i_error("sieve-storage: mkdir_verify: %s", eacces_error_get("stat", dir));
return -1;
} else if ( errno != ENOENT ) {
i_error("sieve-storage: mkdir_verify: stat(%s) failed: %m", dir);
return -1;
}
if ( mkdir_parents_chgrp(dir, mode, gid, gid_origin) == 0 ) {
if ( debug )
i_debug("sieve-storage: created storage directory %s", dir);
return 0;
}
switch ( errno ) {
case EEXIST:
return 0;
case ENOENT:
i_error("sieve-storage: storage was deleted while it was being created");
break;
case EACCES:
i_error("sieve-storage: %s",
eacces_error_get_creating("mkdir_parents_chgrp", dir));
break;
default:
i_error("sieve-storage: mkdir_parents_chgrp(%s) failed: %m", dir);
break;
}
return -1;
}
static int check_tmp(const char *path)
{
struct stat st;
if ( stat(path, &st) < 0 ) {
if ( errno == ENOENT )
return 0;
if ( errno == EACCES ) {
i_error("sieve-storage: check_tmp: %s", eacces_error_get("stat", path));
return -1;
}
i_error("sieve-storage: check_tmp: stat(%s) failed: %m", path);
return -1;
}
if ( st.st_atime > st.st_ctime + SIEVE_STORAGE_TMP_DELETE_SECS ) {
} else if ( st.st_atime < ioloop_time - SIEVE_STORAGE_TMP_SCAN_SECS ) {
(void)unlink_old_files(path, "",
ioloop_time - SIEVE_STORAGE_TMP_DELETE_SECS);
}
return 1;
}
static int _sieve_storage_open_inbox
(struct mail_user *user, struct mailbox **box_r)
{
struct mail_namespace *ns;
struct mailbox *box;
enum mailbox_flags flags = MAILBOX_FLAG_IGNORE_ACLS;
enum mail_error error;
ns = mail_namespace_find_inbox(user->namespaces);
*box_r = box = mailbox_alloc(ns->list, "INBOX", flags);
if (mailbox_open(box) == 0)
return 0;
i_warning("sieve-storage: "
"Failed to open user INBOX for attribute modifications: %s",
mailbox_get_last_error(box, &error));
return -1;
}
static struct sieve_storage *_sieve_storage_create
(struct sieve_instance *svinst, struct mail_user *user, const char *home,
enum sieve_storage_flags flags)
{
pool_t pool;
struct sieve_storage *storage;
struct mailbox *inbox = NULL;
bool debug = ( (flags & SIEVE_STORAGE_FLAG_DEBUG) != 0 );
const char *tmp_dir, *link_path, *path;
const char *sieve_data, *active_path, *active_fname, *storage_dir;
const char *username = user->username;
mode_t dir_create_mode, file_create_mode;
gid_t file_create_gid;
const char *file_create_gid_origin;
unsigned long long int uint_setting;
size_t size_setting;
int ret;
active_path = sieve_setting_get(svinst, "sieve");
if ( active_path != NULL ) {
const char *p;
if ( *active_path == '\0' ) {
if ( debug )
i_debug("sieve-storage: sieve is disabled (sieve=\"\")");
return NULL;
}
p = strchr(active_path, ':');
if ( p != NULL ) {
if ( strncmp(active_path, "file", p-active_path) != 0 ) {
i_error("sieve-storage: Cannot open non-file script location "
"for active script `%s'", active_path);
return NULL;
}
active_path = p+1;
p = strchr(active_path, ';');
if ( p != NULL )
active_path = t_strdup_until(active_path, p);
}
} else {
if ( debug ) {
i_debug("sieve-storage: sieve active script path is unconfigured; "
"using default (sieve=%s)", SIEVE_DEFAULT_PATH);
}
active_path = SIEVE_DEFAULT_PATH;
}
path = home_expand_tilde(active_path, home);
if ( path == NULL ) {
i_error("sieve-storage: userdb(%s) didn't return a home directory "
"for substitition in active script path (sieve=%s)",
username, active_path);
return NULL;
}
active_path = path;
active_fname = strrchr(active_path, '/');
if ( active_fname == NULL )
active_fname = active_path;
else
active_fname++;
if ( *active_fname == '\0' ) {
i_error("sieve-storage: "
"path to active symlink must include the link's filename. Path is: %s",
active_path);
return NULL;
}
storage_dir = NULL;
sieve_data = sieve_setting_get(svinst, "sieve_dir");
if ( sieve_data == NULL )
sieve_data = sieve_setting_get(svinst, "sieve_storage");
if ( sieve_data == NULL || *sieve_data == '\0' ) {
if ( home != NULL && *home != '\0' ) {
if (access(home, R_OK|W_OK|X_OK) == 0) {
if ( debug ) {
i_debug("sieve-storage: root exists (%s)", home);
}
storage_dir = t_strconcat(home, "/sieve", NULL);
} else {
if ( debug ) {
i_debug("sieve-storage: access(%s, rwx): "
"failed: %m", home);
}
}
} else {
if ( debug )
i_debug("sieve-storage: HOME not set");
if (access("/sieve", R_OK|W_OK|X_OK) == 0) {
storage_dir = "/sieve";
if ( debug )
i_debug("sieve-storage: /sieve exists, assuming chroot");
}
}
} else {
const char *p;
p = strchr(sieve_data, ':');
if ( p != NULL ) {
if (strncmp(sieve_data, "file", p-sieve_data) != 0 ) {
i_error("sieve-storage: Cannot open non-file script storage `%s'",
sieve_data);
return NULL;
}
sieve_data = p+1;
p = strchr(sieve_data, ';');
if ( p != NULL )
sieve_data = t_strdup_until(sieve_data, p);
}
storage_dir = sieve_data;
}
if (storage_dir == NULL || *storage_dir == '\0') {
i_error("sieve-storage: couldn't find storage root directory; "
"sieve_dir was left unconfigured and autodetection failed");
return NULL;
}
path = home_expand_tilde(storage_dir, home);
if ( path == NULL ) {
i_error("sieve-storage: userdb(%s) didn't return a home directory "
"for substitition in storage root directory (sieve_dir=%s)",
username, storage_dir);
return NULL;
}
storage_dir = path;
if ( debug ) {
i_debug("sieve-storage: "
"using active sieve script path: %s", active_path);
i_debug("sieve-storage: "
"using sieve script storage directory: %s", storage_dir);
}
struct stat st;
if (stat(storage_dir, &st) < 0) {
if (errno != ENOENT) {
i_error("stat(%s) failed: %m", storage_dir);
return NULL;
}
st.st_mtime = 0;
}
sieve_storage_get_permissions
(storage_dir, &file_create_mode, &dir_create_mode, &file_create_gid,
&file_create_gid_origin, debug);
tmp_dir = t_strconcat(storage_dir, "/tmp", NULL);
if ( (ret=check_tmp(tmp_dir)) < 0 )
return NULL;
if ( ret == 0 && mkdir_verify(tmp_dir, dir_create_mode, file_create_gid,
file_create_gid_origin, debug) < 0 )
return NULL;
if ( (flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 )
(void)_sieve_storage_open_inbox(user, &inbox);
pool = pool_alloconly_create("sieve-storage", 512+256);
storage = p_new(pool, struct sieve_storage, 1);
storage->svinst = svinst;
storage->flags = flags;
storage->pool = pool;
storage->dir = p_strdup(pool, storage_dir);
storage->username = p_strdup(pool, username);
storage->active_path = p_strdup(pool, active_path);
storage->active_fname = p_strdup(pool, active_fname);
storage->prev_mtime = st.st_mtime;
storage->dir_create_mode = dir_create_mode;
storage->file_create_mode = file_create_mode;
storage->file_create_gid = file_create_gid;
storage->inbox = inbox;
link_path = sieve_storage_get_relative_link_path
(storage->active_path, storage->dir);
if ( debug )
i_debug("sieve-storage: "
"relative path to sieve storage in active link: %s", link_path);
storage->link_path = p_strdup(pool, link_path);
storage->max_storage = 0;
storage->max_scripts = 0;
if ( sieve_setting_get_size_value
(svinst, "sieve_quota_max_storage", &size_setting) ) {
storage->max_storage = size_setting;
}
if ( sieve_setting_get_uint_value
(svinst, "sieve_quota_max_scripts", &uint_setting) ) {
storage->max_scripts = uint_setting;
}
if ( debug ) {
if ( storage->max_storage > 0 ) {
i_debug("sieve-storage: quota: storage limit: %llu bytes",
(unsigned long long int) storage->max_storage);
}
if ( storage->max_scripts > 0 ) {
i_debug("sieve-storage: quota: script count limit: %llu scripts",
(unsigned long long int) storage->max_scripts);
}
}
return storage;
}
struct sieve_storage *sieve_storage_create
(struct sieve_instance *svinst, struct mail_user *user, const char *home,
enum sieve_storage_flags flags)
{
struct sieve_storage *storage;
T_BEGIN {
storage = _sieve_storage_create(svinst, user, home, flags);
} T_END;
return storage;
}
void sieve_storage_free(struct sieve_storage *storage)
{
if (storage->inbox != NULL)
mailbox_free(&storage->inbox);
sieve_error_handler_unref(&storage->ehandler);
pool_unref(&storage->pool);
}
int sieve_storage_get_last_change
(struct sieve_storage *storage, time_t *last_change_r)
{
*last_change_r = storage->prev_mtime;
return 0;
}
void sieve_storage_set_modified
(struct sieve_storage *storage, time_t mtime)
{
struct utimbuf times;
time_t cur_mtime;
if ( mtime != (time_t)-1 ) {
if ( sieve_storage_get_last_change(storage, &cur_mtime) >= 0 &&
cur_mtime > mtime )
return;
} else {
mtime = ioloop_time;
}
times.actime = mtime;
times.modtime = mtime;
if ( utime(storage->dir, ×) < 0 ) {
switch ( errno ) {
case ENOENT:
break;
case EACCES:
i_error("sieve-storage: %s", eacces_error_get("utime", storage->dir));
break;
default:
i_error("sieve-storage: utime(%s) failed: %m", storage->dir);
}
} else {
storage->prev_mtime = mtime;
}
}
struct sieve_error_handler *sieve_storage_get_error_handler
(struct sieve_storage *storage)
{
struct sieve_storage_ehandler *ehandler;
if ( storage->ehandler == NULL ) {
pool_t pool = pool_alloconly_create("sieve_storage_ehandler", 512);
ehandler = p_new(pool, struct sieve_storage_ehandler,1);
sieve_error_handler_init(&ehandler->handler, storage->svinst, pool, 1);
ehandler->handler.verror = sieve_storage_verror;
ehandler->storage = storage;
storage->ehandler = (struct sieve_error_handler *) ehandler;
}
return storage->ehandler;
}
static void ATTR_FORMAT(4, 0) sieve_storage_verror
(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
const char *location ATTR_UNUSED, const char *fmt, va_list args)
{
struct sieve_storage_ehandler *sehandler =
(struct sieve_storage_ehandler *) ehandler;
struct sieve_storage *storage = sehandler->storage;
sieve_storage_clear_error(storage);
if (fmt != NULL) {
storage->error = i_strdup_vprintf(fmt, args);
}
storage->error_code = SIEVE_ERROR_TEMP_FAILURE;
}
void sieve_storage_clear_error(struct sieve_storage *storage)
{
i_free(storage->error);
storage->error_code = SIEVE_ERROR_NONE;
storage->error = NULL;
}
void sieve_storage_set_error
(struct sieve_storage *storage, enum sieve_error error,
const char *fmt, ...)
{
va_list va;
sieve_storage_clear_error(storage);
if (fmt != NULL) {
va_start(va, fmt);
storage->error = i_strdup_vprintf(fmt, va);
va_end(va);
}
storage->error_code = error;
}
void sieve_storage_set_critical
(struct sieve_storage *storage, const char *fmt, ...)
{
va_list va;
sieve_storage_clear_error(storage);
if (fmt != NULL) {
i_free(storage->error);
storage->error_code = SIEVE_ERROR_TEMP_FAILURE;
if ( (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 ) {
struct tm *tm;
char str[256];
va_start(va, fmt);
i_error("sieve-storage: %s", t_strdup_vprintf(fmt, va));
va_end(va);
tm = localtime(&ioloop_time);
storage->error =
strftime(str, sizeof(str), CRITICAL_MSG_STAMP, tm) > 0 ?
i_strdup(str) : i_strdup(CRITICAL_MSG);
} else {
va_start(va, fmt);
storage->error = i_strdup_vprintf(fmt, va);
va_end(va);
}
}
}
const char *sieve_storage_get_last_error
(struct sieve_storage *storage, enum sieve_error *error_r)
{
if ( error_r != NULL )
*error_r = storage->error_code;
return storage->error != NULL ? storage->error : "Unknown error";
}
static void sieve_storage_inbox_transaction_finish
(struct sieve_storage *storage, struct mailbox_transaction_context **t)
{
struct mailbox *inbox = storage->inbox;
if (mailbox_transaction_commit(t) < 0) {
enum mail_error error;
i_warning("sieve-storage: Failed to update INBOX attributes: %s",
mail_storage_get_last_error(mailbox_get_storage(inbox), &error));
}
}
void sieve_storage_inbox_script_attribute_set
(struct sieve_storage *storage, const char *name)
{
struct mailbox_transaction_context *t;
const char *key;
if (storage->inbox == NULL)
return;
key = t_strconcat
(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, name, NULL);
t = mailbox_transaction_begin(storage->inbox, 0);
mail_index_attribute_set(t->itrans, TRUE, key, ioloop_time, 0);
sieve_storage_inbox_transaction_finish(storage, &t);
}
void sieve_storage_inbox_script_attribute_rename
(struct sieve_storage *storage, const char *oldname, const char *newname)
{
struct mailbox_transaction_context *t;
const char *oldkey, *newkey;
if (storage->inbox == NULL)
return;
oldkey = t_strconcat
(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, oldname, NULL);
newkey = t_strconcat
(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, newname, NULL);
t = mailbox_transaction_begin(storage->inbox, 0);
mail_index_attribute_unset(t->itrans, TRUE, oldkey, ioloop_time);
mail_index_attribute_set(t->itrans, TRUE, newkey, ioloop_time, 0);
sieve_storage_inbox_transaction_finish(storage, &t);
}
void sieve_storage_inbox_script_attribute_unset
(struct sieve_storage *storage, const char *name)
{
struct mailbox_transaction_context *t;
const char *key;
if (storage->inbox == NULL)
return;
key = t_strconcat
(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, name, NULL);
t = mailbox_transaction_begin(storage->inbox, 0);
mail_index_attribute_unset(t->itrans, TRUE, key, ioloop_time);
sieve_storage_inbox_transaction_finish(storage, &t);
}