summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c')
-rw-r--r--pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c298
1 files changed, 298 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c b/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
new file mode 100644
index 0000000..5af7e04
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
@@ -0,0 +1,298 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "md5.h"
+#include "ioloop.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-message.h"
+#include "sieve-code.h"
+#include "sieve-runtime.h"
+#include "sieve-interpreter.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+
+#include "ext-duplicate-common.h"
+
+/*
+ * Extension configuration
+ */
+
+#define EXT_DUPLICATE_DEFAULT_PERIOD (12*60*60)
+#define EXT_DUPLICATE_DEFAULT_MAX_PERIOD (2*24*60*60)
+
+bool ext_duplicate_load(const struct sieve_extension *ext, void **context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_duplicate_config *config;
+ sieve_number_t default_period, max_period;
+
+ if (*context != NULL)
+ ext_duplicate_unload(ext);
+
+ if (!sieve_setting_get_duration_value(
+ svinst, "sieve_duplicate_default_period", &default_period))
+ default_period = EXT_DUPLICATE_DEFAULT_PERIOD;
+ if (!sieve_setting_get_duration_value(
+ svinst, "sieve_duplicate_max_period", &max_period)) {
+ max_period = EXT_DUPLICATE_DEFAULT_MAX_PERIOD;
+ }
+
+ config = i_new(struct ext_duplicate_config, 1);
+ config->default_period = default_period;
+ config->max_period = max_period;
+
+ *context = (void *) config;
+ return TRUE;
+}
+
+void ext_duplicate_unload(const struct sieve_extension *ext)
+{
+ struct ext_duplicate_config *config =
+ (struct ext_duplicate_config *)ext->context;
+
+ i_free(config);
+}
+
+/*
+ * Duplicate_mark action
+ */
+
+struct act_duplicate_mark_data {
+ const char *handle;
+ unsigned int period;
+ unsigned char hash[MD5_RESULTLEN];
+ bool last:1;
+};
+
+static void
+act_duplicate_mark_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep);
+static void
+act_duplicate_mark_finish(const struct sieve_action_exec_env *aenv,
+ void *tr_context, int status);
+
+static const struct sieve_action_def act_duplicate_mark = {
+ .name = "duplicate_mark",
+ .print = act_duplicate_mark_print,
+ .finish = act_duplicate_mark_finish
+};
+
+static void
+act_duplicate_mark_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ struct act_duplicate_mark_data *data =
+ (struct act_duplicate_mark_data *)action->context;
+ const char *last = (data->last ? " last" : "");
+
+ if (data->handle != NULL) {
+ sieve_result_action_printf(
+ rpenv, "track%s duplicate with handle: %s",
+ last, str_sanitize(data->handle, 128));
+ } else {
+ sieve_result_action_printf(rpenv, "track%s duplicate", last);
+ }
+}
+
+static void
+act_duplicate_mark_finish(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED, int status)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_duplicate_mark_data *data =
+ (struct act_duplicate_mark_data *)aenv->action->context;
+
+ if (status != SIEVE_EXEC_OK) {
+ e_debug(aenv->event, "Not marking duplicate (status=%s)",
+ sieve_execution_exitcode_to_str(status));
+ return;
+ }
+
+ e_debug(aenv->event, "Marking duplicate");
+
+ /* Message was handled successfully, so track duplicate for this
+ * message.
+ */
+ eenv->exec_status->significant_action_executed = TRUE;
+ sieve_action_duplicate_mark(aenv, data->hash, sizeof(data->hash),
+ ioloop_time + data->period);
+}
+
+/*
+ * Duplicate checking
+ */
+
+struct ext_duplicate_handle {
+ const char *handle;
+ bool last:1;
+ bool duplicate:1;
+};
+
+struct ext_duplicate_hash {
+ unsigned char hash[MD5_RESULTLEN];
+ ARRAY(struct ext_duplicate_handle) handles;
+};
+
+struct ext_duplicate_context {
+ ARRAY(struct ext_duplicate_hash) hashes;
+};
+
+static void
+ext_duplicate_hash(string_t *handle, const char *value, size_t value_len,
+ bool last, unsigned char hash_r[])
+{
+ static const char *id = "sieve duplicate";
+ struct md5_context md5ctx;
+
+ md5_init(&md5ctx);
+ md5_update(&md5ctx, id, strlen(id));
+ if (last)
+ md5_update(&md5ctx, "0", 1);
+ else
+ md5_update(&md5ctx, "+", 1);
+ if (handle != NULL) {
+ md5_update(&md5ctx, "h-", 2);
+ md5_update(&md5ctx, str_c(handle), str_len(handle));
+ } else {
+ md5_update(&md5ctx, "default", 7);
+ }
+ md5_update(&md5ctx, value, value_len);
+ md5_final(&md5ctx, hash_r);
+}
+
+int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
+ const char *value, size_t value_len,
+ sieve_number_t period, bool last, bool *duplicate_r)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct ext_duplicate_context *rctx;
+ bool duplicate = FALSE;
+ pool_t msg_pool = NULL, result_pool = NULL;
+ unsigned char hash[MD5_RESULTLEN];
+ struct ext_duplicate_hash *hash_record = NULL;
+ struct ext_duplicate_handle *handle_record = NULL;
+ struct act_duplicate_mark_data *act;
+ int ret;
+
+ *duplicate_r = FALSE;
+
+ if (!sieve_execute_duplicate_check_available(eenv)) {
+ sieve_runtime_warning(
+ renv, NULL, "duplicate test: "
+ "duplicate checking not available in this context");
+ return SIEVE_EXEC_OK;
+ }
+
+ if (value == NULL)
+ return SIEVE_EXEC_OK;
+
+ /* Create hash */
+ ext_duplicate_hash(handle, value, value_len, last, hash);
+
+ /* Get context; find out whether duplicate was checked earlier */
+ rctx = (struct ext_duplicate_context *)
+ sieve_message_context_extension_get(renv->msgctx, this_ext);
+
+ if (rctx == NULL) {
+ /* Create context */
+ msg_pool = sieve_message_context_pool(renv->msgctx);
+ rctx = p_new(msg_pool, struct ext_duplicate_context, 1);
+ sieve_message_context_extension_set(renv->msgctx, this_ext,
+ (void *)rctx);
+ } else if (array_is_created(&rctx->hashes)) {
+ struct ext_duplicate_hash *record;
+
+ array_foreach_modifiable(&rctx->hashes, record) {
+ if (memcmp(record->hash, hash, MD5_RESULTLEN) == 0) {
+ hash_record = record;
+ break;
+ }
+ }
+ }
+ if (hash_record != NULL) {
+ const struct ext_duplicate_handle *rhandle;
+ array_foreach(&hash_record->handles, rhandle) {
+ const char *handle_str =
+ (handle == NULL ? NULL : str_c(handle));
+ if (null_strcmp(rhandle->handle, handle_str) == 0 &&
+ rhandle->last == last)
+ return (rhandle->duplicate ?
+ SIEVE_DUPLICATE_CHECK_RESULT_EXISTS :
+ SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND);
+ }
+ }
+
+ result_pool = sieve_result_pool(renv->result);
+ act = p_new(result_pool, struct act_duplicate_mark_data, 1);
+ if (handle != NULL)
+ act->handle = p_strdup(result_pool, str_c(handle));
+ act->period = period;
+ memcpy(act->hash, hash, MD5_RESULTLEN);
+ act->last = last;
+
+ /* Check duplicate */
+ ret = sieve_execute_duplicate_check(eenv, hash, sizeof(hash),
+ &duplicate);
+ if (ret >= SIEVE_EXEC_OK && !duplicate && last) {
+ unsigned char no_last_hash[MD5_RESULTLEN];
+
+ /* Check for entry without :last */
+ ext_duplicate_hash(handle, value, value_len,
+ FALSE, no_last_hash);
+ ret = sieve_execute_duplicate_check(
+ eenv, no_last_hash, sizeof(no_last_hash),
+ &duplicate);
+ }
+ if (ret < SIEVE_EXEC_OK) {
+ sieve_runtime_critical(
+ renv, NULL, "failed to check for duplicate",
+ "failed to check for duplicate%s",
+ (ret == SIEVE_EXEC_TEMP_FAILURE ?
+ " (temporary failure)" : ""));
+ return ret;
+ }
+
+ /* We may only mark the message as duplicate when Sieve script executes
+ * successfully; therefore defer this operation until successful result
+ * execution.
+ */
+ if (!duplicate || last) {
+ if (sieve_result_add_action(renv, NULL, NULL,
+ &act_duplicate_mark,
+ NULL, (void *) act, 0, FALSE) < 0)
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* Cache result */
+ if (msg_pool == NULL)
+ msg_pool = sieve_message_context_pool(renv->msgctx);
+ if (hash_record == NULL) {
+ if (!array_is_created(&rctx->hashes))
+ p_array_init(&rctx->hashes, msg_pool, 64);
+ hash_record = array_append_space(&rctx->hashes);
+ memcpy(hash_record->hash, hash, MD5_RESULTLEN);
+ p_array_init(&hash_record->handles, msg_pool, 64);
+ }
+
+ handle_record = array_append_space(&hash_record->handles);
+ if (handle != NULL)
+ handle_record->handle = p_strdup(msg_pool, str_c(handle));
+ handle_record->last = last;
+ handle_record->duplicate = duplicate;
+
+ *duplicate_r = duplicate;
+
+ return SIEVE_EXEC_OK;
+}
+