diff options
Diffstat (limited to 'pigeonhole/src/lib-sieve/sieve-script.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/sieve-script.c | 906 |
1 files changed, 906 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/sieve-script.c b/pigeonhole/src/lib-sieve/sieve-script.c new file mode 100644 index 0000000..32963a0 --- /dev/null +++ b/pigeonhole/src/lib-sieve/sieve-script.c @@ -0,0 +1,906 @@ +/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "compat.h" +#include "unichar.h" +#include "str.h" +#include "str-sanitize.h" +#include "hash.h" +#include "array.h" +#include "eacces-error.h" +#include "istream.h" + +#include "sieve-common.h" +#include "sieve-limits.h" +#include "sieve-settings.h" +#include "sieve-error.h" +#include "sieve-dump.h" +#include "sieve-binary.h" + +#include "sieve-storage-private.h" +#include "sieve-script-private.h" + +/* + * 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 minimum length */ + if (namelen == 0) + return FALSE; + + /* Check worst-case maximum length */ + if (namelen > SIEVE_MAX_SCRIPT_NAME_LEN * 4) + 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; + name_chars = array_get(&uni_name, &count); + + /* Check true maximum length */ + if (count > SIEVE_MAX_SCRIPT_NAME_LEN) + return FALSE; + + /* Scan name for invalid characters + * FIXME: compliance with Net-Unicode Definition (Section 2 of + * RFC 5198) is not checked fully and no normalization + * is performed. + */ + for (i = 0; i < count; i++) { + /* 0000-001F; [CONTROL CHARACTERS] */ + if (name_chars[i] <= 0x001f) + return FALSE; + /* 002F; SLASH (not RFC-prohibited, but '/' is dangerous) */ + 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; + /* 00FF */ + if (name_chars[i] == 0x00ff) + return FALSE; + /* 2028; LINE SEPARATOR */ + /* 2029; PARAGRAPH SEPARATOR */ + if (name_chars[i] == 0x2028 || name_chars[i] == 0x2029) + return FALSE; + } + + return TRUE; +} + +/* + * Script instance + */ + +void sieve_script_init(struct sieve_script *script, + struct sieve_storage *storage, + const struct sieve_script *script_class, + const char *location, const char *name) +{ + i_assert(storage != NULL); + + script->script_class = script_class; + script->refcount = 1; + script->storage = storage; + script->location = p_strdup_empty(script->pool, location); + script->name = p_strdup(script->pool, name); + + script->event = event_create(storage->event); + event_add_str(script->event, "script_name", name); + event_add_str(script->event, "script_location", location); + if (name == NULL) + event_set_append_log_prefix(script->event, "script: "); + else { + event_set_append_log_prefix( + script->event, t_strdup_printf("script `%s': ", name)); + } + + sieve_storage_ref(storage); +} + +struct sieve_script * +sieve_script_create(struct sieve_instance *svinst, const char *location, + const char *name, enum sieve_error *error_r) +{ + struct sieve_storage *storage; + struct sieve_script *script; + enum sieve_error error; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + storage = sieve_storage_create(svinst, location, 0, error_r); + if (storage == NULL) + return NULL; + + script = sieve_storage_get_script(storage, name, error_r); + + sieve_storage_unref(&storage); + return script; +} + +void sieve_script_ref(struct sieve_script *script) +{ + script->refcount++; +} + +void sieve_script_unref(struct sieve_script **_script) +{ + struct sieve_script *script = *_script; + + *_script = NULL; + + if (script == NULL) + return; + + i_assert(script->refcount > 0); + if (--script->refcount != 0) + return; + + if (script->stream != NULL) { + struct event_passthrough *e = + event_create_passthrough(script->event)-> + set_name("sieve_script_closed"); + e_debug(e->event(), "Closed script"); + } + i_stream_unref(&script->stream); + + if (script->v.destroy != NULL) + script->v.destroy(script); + + sieve_storage_unref(&script->storage); + event_unref(&script->event); + pool_unref(&script->pool); +} + +int sieve_script_open(struct sieve_script *script, enum sieve_error *error_r) +{ + enum sieve_error error; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + if (script->open) + return 0; + + if (script->v.open(script, error_r) < 0) + return -1; + + i_assert(script->location != NULL); + i_assert(script->name != NULL); + script->open = TRUE; + + if (*script->name != '\0') { + e_debug(script->event, "Opened script `%s' from `%s'", + script->name, script->location); + } else { + e_debug(script->event, "Opened nameless script from `%s'", + script->location); + } + return 0; +} + +int sieve_script_open_as(struct sieve_script *script, const char *name, + enum sieve_error *error_r) +{ + if (sieve_script_open(script, error_r) < 0) + return -1; + + /* override name */ + script->name = p_strdup(script->pool, name); + event_add_str(script->event, "script_name", name); + return 0; +} + +struct sieve_script * +sieve_script_create_open(struct sieve_instance *svinst, const char *location, + const char *name, enum sieve_error *error_r) +{ + struct sieve_script *script; + + script = sieve_script_create(svinst, location, name, error_r); + if (script == NULL) + return NULL; + + if (sieve_script_open(script, error_r) < 0) { + sieve_script_unref(&script); + return NULL; + } + + return script; +} + +int sieve_script_check(struct sieve_instance *svinst, const char *location, + const char *name, enum sieve_error *error_r) +{ + struct sieve_script *script; + enum sieve_error error; + + if (error_r == NULL) + error_r = &error; + + script = sieve_script_create_open(svinst, location, name, error_r); + if (script == NULL) + return (*error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1); + + sieve_script_unref(&script); + return 1; +} + +/* + * Properties + */ + +const char *sieve_script_name(const struct sieve_script *script) +{ + return script->name; +} + +const char *sieve_script_location(const struct sieve_script *script) +{ + return script->location; +} + +struct sieve_instance *sieve_script_svinst(const struct sieve_script *script) +{ + return script->storage->svinst; +} + +int sieve_script_get_size(struct sieve_script *script, uoff_t *size_r) +{ + struct istream *stream; + int ret; + + if (script->v.get_size != NULL) { + if ((ret = script->v.get_size(script, size_r)) != 0) + return ret; + } + + /* Try getting size from the stream */ + if (script->stream == NULL && + sieve_script_get_stream(script, &stream, NULL) < 0) + return -1; + + if (i_stream_get_size(script->stream, TRUE, size_r) < 0) { + sieve_storage_set_critical(script->storage, + "i_stream_get_size(%s) failed: %s", + i_stream_get_name(script->stream), + i_stream_get_error(script->stream)); + return -1; + } + return 0; +} + +bool sieve_script_is_open(const struct sieve_script *script) +{ + return script->open; +} + +bool sieve_script_is_default(const struct sieve_script *script) +{ + return script->storage->is_default; +} + +/* + * Stream management + */ + +int sieve_script_get_stream(struct sieve_script *script, + struct istream **stream_r, + enum sieve_error *error_r) +{ + struct sieve_storage *storage = script->storage; + enum sieve_error error; + int ret; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + if (script->stream != NULL) { + *stream_r = script->stream; + return 0; + } + + // FIXME: necessary? + i_assert(script->open); + + T_BEGIN { + ret = script->v.get_stream(script, &script->stream, error_r); + } T_END; + + if (ret < 0) { + struct event_passthrough *e = + event_create_passthrough(script->event)-> + add_str("error", storage->error)-> + set_name("sieve_script_opened"); + e_debug(e->event(), "Failed to open script for reading: %s", + storage->error); + return -1; + } + + struct event_passthrough *e = + event_create_passthrough(script->event)-> + set_name("sieve_script_opened"); + e_debug(e->event(), "Opened script for reading"); + + *stream_r = script->stream; + return 0; +} + +/* + * Comparison + */ + +bool sieve_script_equals(const struct sieve_script *script, + const struct sieve_script *other) +{ + if (script == other) + return TRUE; + if (script == NULL || other == NULL) + return FALSE; + if (script->script_class != other->script_class) + return FALSE; + + if (script->v.equals == NULL) { + i_assert (script->location != NULL && other->location != NULL); + + return (strcmp(script->location, other->location) == 0); + } + + return script->v.equals(script, other); +} + +unsigned int sieve_script_hash(const struct sieve_script *script) +{ + i_assert(script->name != NULL); + + return str_hash(script->name); +} + +/* + * Binary + */ + +int sieve_script_binary_read_metadata(struct sieve_script *script, + struct sieve_binary_block *sblock, + sieve_size_t *offset) +{ + struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock); + string_t *storage_class, *location; + unsigned int version; + + if ((sieve_binary_block_get_size(sblock) - *offset) == 0) + return 0; + + /* storage class */ + if (!sieve_binary_read_string(sblock, offset, &storage_class)) { + e_error(script->event, + "Binary `%s' has invalid metadata for script `%s': " + "Invalid storage class", + sieve_binary_path(sbin), script->location); + return -1; + } + if (strcmp(str_c(storage_class), script->driver_name) != 0) { + e_debug(script->event, + "Binary `%s' reports unexpected driver name for script `%s' " + "(`%s' rather than `%s')", + sieve_binary_path(sbin), script->location, + str_c(storage_class), script->driver_name); + return 0; + } + + /* version */ + if (!sieve_binary_read_unsigned(sblock, offset, &version)) { + e_error(script->event, + "Binary `%s' has invalid metadata for script `%s': " + "Invalid version", + sieve_binary_path(sbin), script->location); + return -1; + } + if (script->storage->version != version) { + e_debug(script->event, + "Binary `%s' was compiled with " + "a different version of the `%s' script storage class " + "(compiled v%d, expected v%d; " + "automatically fixed when re-compiled)", + sieve_binary_path(sbin), script->driver_name, + version, script->storage->version); + return 0; + } + + /* location */ + if (!sieve_binary_read_string(sblock, offset, &location)) { + e_error(script->event, + "Binary `%s' has invalid metadata for script `%s': " + "Invalid location", + sieve_binary_path(sbin), script->location); + return -1; + } + i_assert(script->location != NULL); + if (strcmp(str_c(location), script->location) != 0) { + e_debug(script->event, + "Binary `%s' reports different location " + "for script `%s' (binary points to `%s')", + sieve_binary_path(sbin), script->location, + str_c(location)); + return 0; + } + + if (script->v.binary_read_metadata == NULL) + return 1; + + return script->v.binary_read_metadata(script, sblock, offset); +} + +void sieve_script_binary_write_metadata(struct sieve_script *script, + struct sieve_binary_block *sblock) +{ + sieve_binary_emit_cstring(sblock, script->driver_name); + sieve_binary_emit_unsigned(sblock, script->storage->version); + sieve_binary_emit_cstring(sblock, (script->location == NULL ? + "" : script->location)); + + if (script->v.binary_write_metadata == NULL) + return; + + script->v.binary_write_metadata(script, sblock); +} + +bool sieve_script_binary_dump_metadata(struct sieve_script *script, + struct sieve_dumptime_env *denv, + struct sieve_binary_block *sblock, + sieve_size_t *offset) +{ + struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock); + struct sieve_instance *svinst = sieve_binary_svinst(sbin); + string_t *storage_class, *location; + struct sieve_script *adhoc_script = NULL; + unsigned int version; + bool result = TRUE; + + /* storage class */ + if (!sieve_binary_read_string(sblock, offset, &storage_class)) + return FALSE; + sieve_binary_dumpf(denv, "class = %s\n", str_c(storage_class)); + + /* version */ + if (!sieve_binary_read_unsigned(sblock, offset, &version)) + return FALSE; + sieve_binary_dumpf(denv, "class.version = %d\n", version); + + /* location */ + if (!sieve_binary_read_string(sblock, offset, &location)) + return FALSE; + sieve_binary_dumpf(denv, "location = %s\n", str_c(location)); + + if (script == NULL) { + script = adhoc_script = + sieve_script_create(svinst, str_c(location), + NULL, NULL); + } + + if (script != NULL && script->v.binary_dump_metadata != NULL) { + result = script->v.binary_dump_metadata( + script, denv, sblock, offset); + } + + if (adhoc_script != NULL) + sieve_script_unref(&adhoc_script); + return result; +} + +struct sieve_binary * +sieve_script_binary_load(struct sieve_script *script, enum sieve_error *error_r) +{ + if (script->v.binary_load == NULL) { + *error_r = SIEVE_ERROR_NOT_POSSIBLE; + return NULL; + } + + return script->v.binary_load(script, error_r); +} + +int sieve_script_binary_save(struct sieve_script *script, + struct sieve_binary *sbin, bool update, + enum sieve_error *error_r) +{ + struct sieve_script *bin_script = sieve_binary_script(sbin); + enum sieve_error error; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + i_assert(bin_script == NULL || sieve_script_equals(bin_script, script)); + + if (script->v.binary_save == NULL) { + *error_r = SIEVE_ERROR_NOT_POSSIBLE; + return -1; + } + + return script->v.binary_save(script, sbin, update, error_r); +} + +const char *sieve_script_binary_get_prefix(struct sieve_script *script) +{ + struct sieve_storage *storage = script->storage; + + if (storage->bin_dir != NULL && + sieve_storage_setup_bindir(storage, 0700) >= 0) { + return t_strconcat(storage->bin_dir, "/", script->name, NULL); + } + + if (script->v.binary_get_prefix == NULL) + return NULL; + + return script->v.binary_get_prefix(script); +} + +/* + * Management + */ + +static int +sieve_script_copy_from_default(struct sieve_script *script, const char *newname) +{ + struct sieve_storage *storage = script->storage; + struct istream *input; + int ret; + + /* copy from default */ + if ((ret = sieve_script_open(script, NULL)) < 0 || + (ret = sieve_script_get_stream(script, &input, NULL)) < 0) { + sieve_storage_copy_error(storage->default_for, storage); + return ret; + } + + ret = sieve_storage_save_as(storage->default_for, input, newname); + if (ret < 0) { + sieve_storage_copy_error(storage, storage->default_for); + } else if (sieve_script_is_active(script) > 0) { + struct sieve_script *newscript; + enum sieve_error error; + + newscript = sieve_storage_open_script(storage->default_for, + newname, &error); + if (newscript == NULL) { + /* Somehow not actually saved */ + ret = (error == SIEVE_ERROR_NOT_FOUND ? 0 : -1); + } else if (sieve_script_activate(newscript, (time_t)-1) < 0) { + /* Failed to activate; roll back */ + ret = -1; + (void)sieve_script_delete(newscript, TRUE); + } + if (newscript != NULL) + sieve_script_unref(&newscript); + + if (ret < 0) { + e_error(storage->event, + "Failed to implicitly activate script `%s' " + "after rename", newname); + sieve_storage_copy_error(storage->default_for, storage); + } + } + + return ret; +} + +int sieve_script_rename(struct sieve_script *script, const char *newname) +{ + struct sieve_storage *storage = script->storage; + const char *oldname = script->name; + struct event_passthrough *event; + int ret; + + i_assert(newname != NULL); + + /* Check script name */ + if (!sieve_script_name_is_valid(newname)) { + sieve_script_set_error(script, + SIEVE_ERROR_BAD_PARAMS, + "Invalid new Sieve script name `%s'.", + str_sanitize(newname, 80)); + return -1; + } + + i_assert(script->open); // FIXME: auto-open? + + if (storage->default_for == NULL) { + i_assert((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0); + + /* rename script */ + i_assert(script->v.rename != NULL); + ret = script->v.rename(script, newname); + + /* rename INBOX mailbox attribute */ + if (ret >= 0 && oldname != NULL) { + (void)sieve_storage_sync_script_rename(storage, oldname, + newname); + } + } else if (sieve_storage_check_script(storage->default_for, + newname, NULL) > 0) { + sieve_script_set_error(script, SIEVE_ERROR_EXISTS, + "A sieve script with that name already exists."); + sieve_storage_copy_error(storage->default_for, storage); + ret = -1; + } else { + ret = sieve_script_copy_from_default(script, newname); + } + + event = event_create_passthrough(script->event)-> + clear_field("script_name")-> + add_str("old_script_name", script->name)-> + add_str("new_script_name", newname)-> + set_name("sieve_script_renamed"); + + if (ret >= 0) { + e_debug(event->event(), "Script renamed to `%s'", newname); + } else { + event = event->add_str("error", storage->error); + + e_debug(event->event(), "Failed to rename script: %s", + storage->error); + } + + return ret; +} + +int sieve_script_delete(struct sieve_script *script, bool ignore_active) +{ + struct sieve_storage *storage = script->storage; + bool is_active = FALSE; + int ret = 0; + + i_assert(script->open); // FIXME: auto-open? + + /* Is the requested script active? */ + if (sieve_script_is_active(script) > 0) { + is_active = TRUE; + if (!ignore_active) { + sieve_script_set_error(script, SIEVE_ERROR_ACTIVE, + "Cannot delete the active Sieve script."); + if (storage->default_for != NULL) { + sieve_storage_copy_error(storage->default_for, + storage); + } + return -1; + } + } + + /* Trying to delete the default script? */ + if (storage->is_default) { + /* ignore */ + return 0; + } + + i_assert((script->storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0); + + /* Deactivate it explicity */ + if (ignore_active && is_active) + (void)sieve_storage_deactivate(storage, (time_t)-1); + + i_assert(script->v.delete != NULL); + ret = script->v.delete(script); + + if (ret >= 0) { + struct event_passthrough *e = + event_create_passthrough(script->event)-> + set_name("sieve_script_deleted"); + e_debug(e->event(), "Script deleted"); + + /* unset INBOX mailbox attribute */ + (void)sieve_storage_sync_script_delete(storage, script->name); + } else { + struct event_passthrough *e = + event_create_passthrough(script->event)-> + add_str("error", storage->error)-> + set_name("sieve_script_deleted"); + e_debug(e->event(), "Failed to delete script: %s", + storage->error); + } + return ret; +} + +int sieve_script_is_active(struct sieve_script *script) +{ + struct sieve_storage *storage = script->storage; + + /* Special handling if this is a default script */ + if (storage->default_for != NULL) { + int ret = sieve_storage_active_script_is_default( + storage->default_for); + if (ret < 0) + sieve_storage_copy_error(storage, storage->default_for); + return ret; + } + + if (script->v.is_active == NULL) + return 0; + return script->v.is_active(script); +} + +int sieve_script_activate(struct sieve_script *script, time_t mtime) +{ + struct sieve_storage *storage = script->storage; + int ret = 0; + + i_assert(script->open); // FIXME: auto-open? + + if (storage->default_for == NULL) { + i_assert((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0); + + i_assert(script->v.activate != NULL); + ret = script->v.activate(script); + + if (ret >= 0) { + struct event_passthrough *e = + event_create_passthrough(script->event)-> + set_name("sieve_script_activated"); + e_debug(e->event(), "Script activated"); + + sieve_storage_set_modified(storage, mtime); + (void)sieve_storage_sync_script_activate(storage); + } else { + struct event_passthrough *e = + event_create_passthrough(script->event)-> + add_str("error", storage->error)-> + set_name("sieve_script_activated"); + e_debug(e->event(), "Failed to activate script: %s", + storage->error); + } + + } else { + /* Activating the default script is equal to deactivating + the storage */ + ret = sieve_storage_deactivate(storage->default_for, + (time_t)-1); + if (ret < 0) + sieve_storage_copy_error(storage, storage->default_for); + } + + return ret; +} + +/* + * Error handling + */ + +void sieve_script_set_error(struct sieve_script *script, enum sieve_error error, + const char *fmt, ...) +{ + struct sieve_storage *storage = script->storage; + 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_script_set_internal_error(struct sieve_script *script) +{ + sieve_storage_set_internal_error(script->storage); +} + +void sieve_script_set_critical(struct sieve_script *script, + const char *fmt, ...) +{ + struct sieve_storage *storage = script->storage; + + va_list va; + + if (fmt != NULL) { + if ((storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0) { + va_start(va, fmt); + e_error(script->event, "%s", t_strdup_vprintf(fmt, va)); + va_end(va); + + sieve_storage_set_internal_error(storage); + + } else { + sieve_storage_clear_error(storage); + + /* no user is involved while synchronizing, so do it the + normal way */ + va_start(va, fmt); + storage->error = i_strdup_vprintf(fmt, va); + va_end(va); + + storage->error_code = SIEVE_ERROR_TEMP_FAILURE; + } + } +} + +const char * +sieve_script_get_last_error(struct sieve_script *script, + enum sieve_error *error_r) +{ + return sieve_storage_get_last_error(script->storage, error_r); +} + +const char *sieve_script_get_last_error_lcase(struct sieve_script *script) +{ + return sieve_error_from_external(script->storage->error); +} + +/* + * Script sequence + */ + +void sieve_script_sequence_init(struct sieve_script_sequence *seq, + struct sieve_storage *storage) +{ + seq->storage = storage; + sieve_storage_ref(storage); +} + +struct sieve_script_sequence * +sieve_script_sequence_create(struct sieve_instance *svinst, + const char *location, enum sieve_error *error_r) +{ + struct sieve_storage *storage; + struct sieve_script_sequence *seq; + enum sieve_error error; + + if (error_r != NULL) + *error_r = SIEVE_ERROR_NONE; + else + error_r = &error; + + storage = sieve_storage_create(svinst, location, 0, error_r); + if (storage == NULL) + return NULL; + + seq = sieve_storage_get_script_sequence(storage, error_r); + + sieve_storage_unref(&storage); + return seq; +} + +struct sieve_script * +sieve_script_sequence_next(struct sieve_script_sequence *seq, + enum sieve_error *error_r) +{ + struct sieve_storage *storage = seq->storage; + + i_assert(storage->v.script_sequence_next != NULL); + return storage->v.script_sequence_next(seq, error_r); +} + +void sieve_script_sequence_free(struct sieve_script_sequence **_seq) +{ + struct sieve_script_sequence *seq = *_seq; + struct sieve_storage *storage = seq->storage; + + if (storage->v.script_sequence_destroy != NULL) + storage->v.script_sequence_destroy(seq); + + sieve_storage_unref(&storage); + *_seq = NULL; +} + |