#include "lib.h"
#include "home-expand.h"
#include "ioloop.h"
#include "mkdir-parents.h"
#include "eacces-error.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>
#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: %s", eacces_error_get("stat", dir));
return -1;
} else if ( errno != ENOENT ) {
i_error("sieve-storage: 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", dir));
break;
default:
i_error("sieve-storage: mkdir(%s) failed: %m", dir);
break;
}
return -1;
}
static struct sieve_storage *_sieve_storage_create
(struct sieve_instance *svinst, const char *user, const char *home, bool debug)
{
pool_t pool;
struct sieve_storage *storage;
const char *tmp_dir, *link_path, *path;
const char *sieve_data, *active_path, *active_fname, *storage_dir;
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;
active_path = sieve_setting_get(svinst, "sieve");
if ( active_path != NULL ) {
if ( *active_path == '\0' ) {
if ( debug )
i_debug("sieve-storage: sieve is disabled (sieve=\"\")");
return NULL;
}
} 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)", user, 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 {
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)",
user, 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);
}
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 ( mkdir_verify(tmp_dir, dir_create_mode, file_create_gid,
file_create_gid_origin, debug) < 0 )
return NULL;
pool = pool_alloconly_create("sieve-storage", 512+256);
storage = p_new(pool, struct sieve_storage, 1);
storage->svinst = svinst;
storage->debug = debug;
storage->pool = pool;
storage->dir = p_strdup(pool, storage_dir);
storage->user = p_strdup(pool, user);
storage->active_path = p_strdup(pool, active_path);
storage->active_fname = p_strdup(pool, active_fname);
storage->dir_create_mode = dir_create_mode;
storage->file_create_mode = file_create_mode;
storage->file_create_gid = file_create_gid;
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_info("sieve-storage: quota: storage limit: %llu bytes",
(unsigned long long int) storage->max_storage);
}
if ( storage->max_scripts > 0 ) {
i_info("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, const char *user, const char *home, bool debug)
{
struct sieve_storage *storage;
T_BEGIN {
storage = _sieve_storage_create(svinst, user, home, debug);
} T_END;
return storage;
}
void sieve_storage_free(struct sieve_storage *storage)
{
sieve_error_handler_unref(&storage->ehandler);
pool_unref(&storage->pool);
}
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 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_FAIL;
}
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_internal_error(struct sieve_storage *storage)
{
struct tm *tm;
char str[256];
tm = localtime(&ioloop_time);
i_free(storage->error);
storage->error_code = SIEVE_ERROR_TEMP_FAIL;
storage->error =
strftime(str, sizeof(str), CRITICAL_MSG_STAMP, tm) > 0 ?
i_strdup(str) : i_strdup(CRITICAL_MSG);
}
void sieve_storage_set_critical
(struct sieve_storage *storage, const char *fmt, ...)
{
va_list va;
sieve_storage_clear_error(storage);
if (fmt != NULL) {
va_start(va, fmt);
i_error("sieve-storage: %s", t_strdup_vprintf(fmt, va));
va_end(va);
sieve_storage_set_internal_error(storage);
}
}
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";
}