Skip to content
Snippets Groups Projects
sieve-script.c 9.73 KiB
/* Copyright (c) 2002-2010 Dovecot Sieve authors, see the included COPYING file
 */

#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>

/*
 * Configuration
 */
 
#define SIEVE_READ_BLOCK_SIZE (1024*8)

/*
 * Script name
 */

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);

	/* Check maximum length */
	if ( namelen > SIEVE_MAX_SCRIPT_NAME_LEN )
		return FALSE;

	/* Intialize array for unicode characters */
	t_array_init(&uni_name, namelen * 4);

	/* Convert UTF-8 to UCS4/UTF-32 */
	if ( uni_utf8_to_ucs4(scriptname, &uni_name) < 0 )
		return FALSE;

	/* Scan name for invalid characters */
	name_chars = array_get(&uni_name, &count);
	for ( i = 0; i < count; i++ ) {

		/* 0000-001F; [CONTROL CHARACTERS] */
		if ( name_chars[i] <= 0x001f )
			return FALSE;
		
		/* 002F; SLASH */
		if ( name_chars[i] == 0x002f )
			return FALSE;

		/* 007F; DELETE */
		if ( name_chars[i] == 0x007f )
			return FALSE;

		/* 0080-009F; [CONTROL CHARACTERS] */
		if ( name_chars[i] >= 0x0080 && name_chars[i] <= 0x009f )
			return FALSE;

		/* 2028; LINE SEPARATOR */
		/* 2029; PARAGRAPH SEPARATOR */
		if ( name_chars[i] == 0x2028 || name_chars[i] == 0x2029 )
			return FALSE;
	}

	return TRUE;
}

/*
 * Filename to name/name to filename
 */

static inline const char *_sieve_scriptfile_get_basename(const char *filename)
{
	const char *ext;

	/* Extract the script name */
	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;

 	/* See if it ends in .sieve already */
	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;
}


/* 
 * Script object 
 */
 
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, 
	bool *exists_r)
{
	int ret;
	pool_t pool;
	struct stat st;
	struct stat lnk_st;
	const char *filename, *dirpath, *basename, *binpath;

	if ( exists_r != NULL )
		*exists_r = TRUE;

	T_BEGIN {

		/* Extract filename from path */

		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;
		}
			
		/* First obtain stat data from the system */
		
		if ( (ret=lstat(path, &st)) < 0 ) {
			switch ( errno ) {
			case ENOENT:
				if ( exists_r == NULL ) {
					sieve_error(ehandler, basename, "sieve script does not exist");
					if ( svinst->debug )
						sieve_sys_debug("script file %s not found", t_abspath(path));
				} else
					*exists_r = FALSE;
				break;
			case EACCES:
				sieve_critical(ehandler, basename, 
					"failed to stat sieve script: %s",
					eacces_error_get("lstat", path));
				break;
            default:
				sieve_critical(ehandler, basename, 
					"failed to stat sieve script: lstat(%s) failed: %m", path);
			}

			script = NULL;
			ret = 1;

		} else {
			/* Record stat information from the symlink */
			lnk_st = st;

			/* Only create/init the object if it stat()s without problems */
			if (S_ISLNK(st.st_mode)) {
				if ( (ret=stat(path, &st)) < 0 ) { 
					switch ( errno ) {
					case ENOENT:
						if ( exists_r == NULL )
							sieve_error(ehandler, basename, "sieve script does not exist");
						else
							*exists_r = FALSE;
						break;
					case EACCES:
						sieve_critical(ehandler, basename, 
							"failed to stat sieve script: %s",
							eacces_error_get("stat", path));
						break;
            		default:
						sieve_critical(ehandler, basename, 
							"failed to stat sieve script: stat(%s) failed: %m", path);
						break;
					}

					script = NULL;	
					ret = 1;
				}
			}

			if ( ret == 0 && !S_ISREG(st.st_mode) ) {
				sieve_critical(ehandler, basename, 
					"sieve script file '%s' is not a regular file.", path);
				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, bool *exists_r)
{
	return sieve_script_init(NULL, svinst, path, name, ehandler, exists_r);
}

struct sieve_script *sieve_script_create_in_directory
(struct sieve_instance *svinst, const char *dirpath, const char *name,
	struct sieve_error_handler *ehandler, bool *exists_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, exists_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;
}

/* 
 * Accessors 
 */

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;
}

/* 
 * Stream manageement 
 */

struct istream *sieve_script_open
(struct sieve_script *script, bool *deleted_r)
{
	int fd;
	struct stat st;
	struct istream *result;

	if ( deleted_r != NULL )
		*deleted_r = FALSE;

	if ( (fd=open(script->path, O_RDONLY)) < 0 ) {
		switch( errno ) {
		case ENOENT:
			if ( deleted_r == NULL ) 
				/* Not supposed to occur, create() does stat already */
				sieve_error(script->ehandler, script->basename, 
					"sieve script does not exist");
			else 
				*deleted_r = TRUE;
			break;
		case EACCES:
			sieve_critical(script->ehandler, script->path,
				"failed to open sieve script: %s",
				eacces_error_get("open", script->path));
			break;
		default:
			sieve_critical(script->ehandler, script->path, 
				"failed to open sieve script: open(%s) failed: %m", script->path);
			break;
		}
		return NULL;
	}	
	
	if ( fstat(fd, &st) != 0 ) {
		sieve_critical(script->ehandler, script->path, 
			"failed to open sieve script: fstat(fd=%s) failed: %m", script->path);
		result = NULL;
	} else {
		/* Re-check the file type just to be sure */
		if ( !S_ISREG(st.st_mode) ) {
			sieve_critical(script->ehandler, script->path,
				"sieve script file '%s' is not a regular file", script->path);
			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 ) {
		/* Something went wrong, close the fd */
		if ( close(fd) != 0 ) {
			sieve_sys_error(
				"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;
}

/* 
 * Comparison 
 */

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 );
}

Consent

On this website, we use the web analytics service Matomo to analyze and review the use of our website. Through the collected statistics, we can improve our offerings and make them more appealing for you. Here, you can decide whether to allow us to process your data and set corresponding cookies for these purposes, in addition to technically necessary cookies. Further information on data protection—especially regarding "cookies" and "Matomo"—can be found in our privacy policy. You can withdraw your consent at any time.