#include "lib.h"
#include "compat.h"
#include "unichar.h"
#include "array.h"
#include "abspath.h"
#include "istream.h"
#include "eacces-error.h"
#include "sieve-common.h"
#include "sieve-limits.h"
#include "sieve-error.h"
#include "sieve-script-private.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SIEVE_READ_BLOCK_SIZE (1024*8)
bool sieve_script_name_is_valid(const char *scriptname)
{
ARRAY_TYPE(unichars) uni_name;
unsigned int count, i;
const unichar_t *name_chars;
size_t namelen = strlen(scriptname);
if ( namelen == 0 )
return FALSE;
if ( namelen > SIEVE_MAX_SCRIPT_NAME_LEN * 4 )
return FALSE;
t_array_init(&uni_name, namelen * 4);
if ( uni_utf8_to_ucs4(scriptname, &uni_name) < 0 )
return FALSE;
name_chars = array_get(&uni_name, &count);
if ( count > SIEVE_MAX_SCRIPT_NAME_LEN )
return FALSE;
for ( i = 0; i < count; i++ ) {
if ( name_chars[i] <= 0x001f )
return FALSE;
if ( name_chars[i] == 0x002f )
return FALSE;
if ( name_chars[i] == 0x007f )
return FALSE;
if ( name_chars[i] >= 0x0080 && name_chars[i] <= 0x009f )
return FALSE;
if ( name_chars[i] == 0x00ff )
return FALSE;
if ( name_chars[i] == 0x2028 || name_chars[i] == 0x2029 )
return FALSE;
}
return TRUE;
}
static inline const char *_sieve_scriptfile_get_basename(const char *filename)
{
const char *ext;
ext = strrchr(filename, '.');
if ( ext == NULL || ext == filename || strncmp(ext,".sieve",6) != 0 )
return filename;
return t_strdup_until(filename, ext);
}
bool sieve_script_file_has_extension(const char *filename)
{
const char *ext;
ext = strrchr(filename, '.');
if ( ext == NULL || ext == filename || strncmp(ext,".sieve",6) != 0 )
return FALSE;
return TRUE;
}
static inline const char *_sieve_scriptfile_from_name(const char *name)
{
if ( !sieve_script_file_has_extension(name) )
return t_strconcat(name, ".sieve", NULL);
return name;
}
static void sieve_script_handle_file_error
(struct sieve_instance *svinst, const char *path, const char *name,
struct sieve_error_handler *ehandler, enum sieve_error *error_r)
{
switch ( errno ) {
case ENOENT:
if ( error_r == NULL )
sieve_error(ehandler, name, "sieve script does not exist");
else {
if ( svinst->debug )
sieve_sys_debug(svinst, "script file %s not found", t_abspath(path));
*error_r = SIEVE_ERROR_NOT_FOUND;
}
break;
case EACCES:
sieve_critical(svinst, ehandler, name, "failed to open sieve script",
"failed to stat sieve script: %s", eacces_error_get("stat", path));
if ( error_r != NULL )
*error_r = SIEVE_ERROR_NO_PERM;
break;
default:
sieve_critical(svinst, ehandler, name, "failed to open sieve script",
"failed to stat sieve script: stat(%s) failed: %m", path);
if ( error_r != NULL )
*error_r = SIEVE_ERROR_TEMP_FAIL;
break;
}
}
struct sieve_script *sieve_script_init
(struct sieve_script *script, struct sieve_instance *svinst,
const char *path, const char *name, struct sieve_error_handler *ehandler,
enum sieve_error *error_r)
{
int ret;
pool_t pool;
struct stat st;
struct stat lnk_st;
const char *filename, *dirpath, *basename, *binpath;
if ( error_r != NULL )
*error_r = SIEVE_ERROR_NONE;
T_BEGIN {
filename = strrchr(path, '/');
if ( filename == NULL ) {
dirpath = "";
filename = path;
} else {
dirpath = t_strdup_until(path, filename);
filename++;
}
basename = _sieve_scriptfile_get_basename(filename);
if ( *dirpath == '\0' )
binpath = t_strconcat(basename, ".svbin", NULL);
else
binpath = t_strconcat(dirpath, "/", basename, ".svbin", NULL);
if ( name == NULL ) {
name = basename;
} else if ( *name == '\0' ) {
name = NULL;
} else {
basename = name;
}
if ( (ret=lstat(path, &st)) < 0 ) {
sieve_script_handle_file_error(svinst, path, basename, ehandler, error_r);
script = NULL;
ret = 1;
} else {
lnk_st = st;
if ( S_ISLNK(st.st_mode) && (ret=stat(path, &st)) < 0 ) {
sieve_script_handle_file_error
(svinst, path, basename, ehandler, error_r);
script = NULL;
ret = 1;
}
if ( ret == 0 && !S_ISREG(st.st_mode) ) {
sieve_critical(svinst, ehandler, basename,
"failed to open sieve script",
"sieve script file '%s' is not a regular file.", path);
if ( error_r != NULL )
*error_r = SIEVE_ERROR_NOT_POSSIBLE;
script = NULL;
ret = 1;
}
}
if ( ret <= 0 ) {
if ( script == NULL ) {
pool = pool_alloconly_create("sieve_script", 1024);
script = p_new(pool, struct sieve_script, 1);
script->pool = pool;
} else
pool = script->pool;
script->refcount = 1;
script->svinst = svinst;
script->ehandler = ehandler;
sieve_error_handler_ref(ehandler);
script->st = st;
script->lnk_st = lnk_st;
script->path = p_strdup(pool, path);
script->filename = p_strdup(pool, filename);
script->dirpath = p_strdup(pool, dirpath);
script->binpath = p_strdup(pool, binpath);
script->basename = p_strdup(pool, basename);
if ( name != NULL )
script->name = p_strdup(pool, name);
else
script->name = NULL;
}
} T_END;
return script;
}
struct sieve_script *sieve_script_create
(struct sieve_instance *svinst, const char *path, const char *name,
struct sieve_error_handler *ehandler, enum sieve_error *error_r)
{
return sieve_script_init(NULL, svinst, path, name, ehandler, error_r);
}
struct sieve_script *sieve_script_create_in_directory
(struct sieve_instance *svinst, const char *dirpath, const char *name,
struct sieve_error_handler *ehandler, enum sieve_error *error_r)
{
const char *path;
if ( dirpath[strlen(dirpath)-1] == '/' )
path = t_strconcat(dirpath,
_sieve_scriptfile_from_name(name), NULL);
else
path = t_strconcat(dirpath, "/",
_sieve_scriptfile_from_name(name), NULL);
return sieve_script_init(NULL, svinst, path, name, ehandler, error_r);
}
void sieve_script_ref(struct sieve_script *script)
{
script->refcount++;
}
void sieve_script_unref(struct sieve_script **script)
{
i_assert((*script)->refcount > 0);
if (--(*script)->refcount != 0)
return;
if ( (*script)->stream != NULL )
i_stream_destroy(&(*script)->stream);
sieve_error_handler_unref(&(*script)->ehandler);
pool_unref(&(*script)->pool);
*script = NULL;
}
const char *sieve_script_name(const struct sieve_script *script)
{
return script->name;
}
const char *sieve_script_filename(const struct sieve_script *script)
{
return script->filename;
}
const char *sieve_script_path(const struct sieve_script *script)
{
return script->path;
}
const char *sieve_script_dirpath(const struct sieve_script *script)
{
return script->dirpath;
}
const char *sieve_script_binpath(const struct sieve_script *script)
{
return script->binpath;
}
mode_t sieve_script_permissions(const struct sieve_script *script)
{
return script->st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
}
struct sieve_instance *sieve_script_svinst(const struct sieve_script *script)
{
return script->svinst;
}
size_t sieve_script_size(const struct sieve_script *script)
{
return script->st.st_size;
}
struct istream *sieve_script_open
(struct sieve_script *script, enum sieve_error *error_r)
{
int fd;
struct stat st;
struct istream *result;
if ( error_r != NULL )
*error_r = SIEVE_ERROR_NONE;
if ( (fd=open(script->path, O_RDONLY)) < 0 ) {
sieve_script_handle_file_error
(script->svinst, script->path, script->basename, script->ehandler,
error_r);
return NULL;
}
if ( fstat(fd, &st) != 0 ) {
sieve_critical(script->svinst, script->ehandler, script->basename,
"failed to open sieve script",
"failed to open sieve script: fstat(fd=%s) failed: %m", script->path);
if ( error_r != NULL )
*error_r = SIEVE_ERROR_TEMP_FAIL;
result = NULL;
} else {
if ( !S_ISREG(st.st_mode) ) {
sieve_critical(script->svinst, script->ehandler, script->basename,
"failed to open sieve script",
"sieve script file '%s' is not a regular file", script->path);
if ( error_r != NULL )
*error_r = SIEVE_ERROR_NOT_POSSIBLE;
result = NULL;
} else {
result = script->stream =
i_stream_create_fd(fd, SIEVE_READ_BLOCK_SIZE, TRUE);
script->st = script->lnk_st = st;
}
}
if ( result == NULL ) {
if ( close(fd) != 0 ) {
sieve_sys_error(script->svinst,
"failed to close sieve script: close(fd=%s) failed: %m",
script->path);
}
}
return result;
}
void sieve_script_close(struct sieve_script *script)
{
i_stream_destroy(&script->stream);
}
uoff_t sieve_script_get_size(const struct sieve_script *script)
{
return script->st.st_size;
}
int sieve_script_cmp
(const struct sieve_script *script1, const struct sieve_script *script2)
{
if ( script1 == NULL || script2 == NULL )
return -1;
return ( script1->st.st_ino == script2->st.st_ino ) ? 0 : -1;
}
unsigned int sieve_script_hash(const struct sieve_script *script)
{
return (unsigned int) script->st.st_ino;
}
bool sieve_script_newer
(const struct sieve_script *script, time_t time)
{
return ( script->st.st_mtime > time || script->lnk_st.st_mtime > time );
}