summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c')
-rw-r--r--pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c385
1 files changed, 385 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c b/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c
new file mode 100644
index 0000000..f0c630e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c
@@ -0,0 +1,385 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':regex'
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-ast.h"
+#include "sieve-stringlist.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-match.h"
+
+#include "ext-regex-common.h"
+
+#include <sys/types.h>
+#include <regex.h>
+#include <ctype.h>
+
+/*
+ * Configuration
+ */
+
+#define MCHT_REGEX_MAX_SUBSTITUTIONS SIEVE_MAX_MATCH_VALUES
+
+/*
+ * Match type
+ */
+
+static bool mcht_regex_validate_context
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ struct sieve_match_type_context *ctx, struct sieve_ast_argument *key_arg);
+
+static void mcht_regex_match_init(struct sieve_match_context *mctx);
+static int mcht_regex_match_keys
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+ struct sieve_stringlist *key_list);
+static void mcht_regex_match_deinit(struct sieve_match_context *mctx);
+
+const struct sieve_match_type_def regex_match_type = {
+ SIEVE_OBJECT("regex", &regex_match_type_operand, 0),
+ .validate_context = mcht_regex_validate_context,
+ .match_init = mcht_regex_match_init,
+ .match_keys = mcht_regex_match_keys,
+ .match_deinit = mcht_regex_match_deinit
+};
+
+/*
+ * Match type validation
+ */
+
+/* Wrapper around the regerror function for easy access */
+static const char *_regexp_error(regex_t *regexp, int errorcode)
+{
+ size_t errsize = regerror(errorcode, regexp, NULL, 0);
+
+ if ( errsize > 0 ) {
+ char *errbuf;
+
+ buffer_t *error_buf =
+ buffer_create_dynamic(pool_datastack_create(), errsize);
+ errbuf = buffer_get_space_unsafe(error_buf, 0, errsize);
+
+ errsize = regerror(errorcode, regexp, errbuf, errsize);
+
+ /* We don't want the error to start with a capital letter */
+ errbuf[0] = i_tolower(errbuf[0]);
+
+ buffer_append_space_unsafe(error_buf, errsize);
+
+ return str_c(error_buf);
+ }
+
+ return "";
+}
+
+static int mcht_regex_validate_regexp
+(struct sieve_validator *valdtr,
+ struct sieve_match_type_context *mtctx ATTR_UNUSED,
+ struct sieve_ast_argument *key, int cflags)
+{
+ int ret;
+ regex_t regexp;
+ const char *regex_str = sieve_ast_argument_strc(key);
+
+ if ( (ret=regcomp(&regexp, regex_str, cflags)) != 0 ) {
+ sieve_argument_validate_error(valdtr, key,
+ "invalid regular expression '%s' for regex match: %s",
+ str_sanitize(regex_str, 128), _regexp_error(&regexp, ret));
+
+ regfree(&regexp);
+ return -1;
+ }
+
+ regfree(&regexp);
+ return 1;
+}
+
+struct _regex_key_context {
+ struct sieve_validator *valdtr;
+ struct sieve_match_type_context *mtctx;
+ int cflags;
+};
+
+static int mcht_regex_validate_key_argument
+(void *context, struct sieve_ast_argument *key)
+{
+ struct _regex_key_context *keyctx = (struct _regex_key_context *) context;
+
+ /* FIXME: We can currently only handle string literal argument, so
+ * variables are not allowed.
+ */
+ if ( sieve_argument_is_string_literal(key) ) {
+ return mcht_regex_validate_regexp
+ (keyctx->valdtr, keyctx->mtctx, key, keyctx->cflags);
+ }
+
+ return 1;
+}
+
+static bool mcht_regex_validate_context
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg ATTR_UNUSED,
+ struct sieve_match_type_context *mtctx, struct sieve_ast_argument *key_arg)
+{
+ const struct sieve_comparator *cmp = mtctx->comparator;
+ int cflags = REG_EXTENDED | REG_NOSUB;
+ struct _regex_key_context keyctx;
+ struct sieve_ast_argument *kitem;
+
+ if ( cmp != NULL ) {
+ if ( sieve_comparator_is(cmp, i_ascii_casemap_comparator) )
+ cflags = REG_EXTENDED | REG_NOSUB | REG_ICASE;
+ else if ( sieve_comparator_is(cmp, i_octet_comparator) )
+ cflags = REG_EXTENDED | REG_NOSUB;
+ else {
+ sieve_argument_validate_error(valdtr, mtctx->argument,
+ "regex match type only supports "
+ "i;octet and i;ascii-casemap comparators" );
+ return FALSE;
+ }
+ }
+
+ /* Validate regular expression keys */
+
+ keyctx.valdtr = valdtr;
+ keyctx.mtctx = mtctx;
+ keyctx.cflags = cflags;
+
+ kitem = key_arg;
+ if ( sieve_ast_stringlist_map(&kitem, (void *) &keyctx,
+ mcht_regex_validate_key_argument) <= 0 )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Match type implementation
+ */
+
+struct mcht_regex_key {
+ regex_t regexp;
+ int status;
+};
+
+struct mcht_regex_context {
+ ARRAY(struct mcht_regex_key) reg_expressions;
+ regmatch_t *pmatch;
+ size_t nmatch;
+ bool all_compiled:1;
+};
+
+static void mcht_regex_match_init
+(struct sieve_match_context *mctx)
+{
+ pool_t pool = mctx->pool;
+ struct mcht_regex_context *ctx;
+
+ /* Create context */
+ ctx = p_new(pool, struct mcht_regex_context, 1);
+
+ /* Create storage for match values if match values are requested */
+ if ( sieve_match_values_are_enabled(mctx->runenv) ) {
+ ctx->pmatch = p_new(pool, regmatch_t, MCHT_REGEX_MAX_SUBSTITUTIONS);
+ ctx->nmatch = MCHT_REGEX_MAX_SUBSTITUTIONS;
+ } else {
+ ctx->pmatch = NULL;
+ ctx->nmatch = 0;
+ }
+
+ /* Assign context */
+ mctx->data = (void *) ctx;
+}
+
+static int mcht_regex_match_key
+(struct sieve_match_context *mctx, const char *val,
+ const regex_t *regexp)
+{
+ struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+ int ret;
+
+ /* Execute regex */
+
+ ret = regexec(regexp, val, ctx->nmatch, ctx->pmatch, 0);
+
+ /* Handle match values if necessary */
+
+ if ( ret == 0 ) {
+ if ( ctx->nmatch > 0 ) {
+ struct sieve_match_values *mvalues;
+ size_t i;
+ int skipped = 0;
+ string_t *subst = t_str_new(32);
+
+ /* Start new list of match values */
+ mvalues = sieve_match_values_start(mctx->runenv);
+
+ i_assert( mvalues != NULL );
+
+ /* Add match values from regular expression */
+ for ( i = 0; i < ctx->nmatch; i++ ) {
+ str_truncate(subst, 0);
+
+ if ( ctx->pmatch[i].rm_so != -1 ) {
+ if ( skipped > 0 ) {
+ sieve_match_values_skip(mvalues, skipped);
+ skipped = 0;
+ }
+
+ str_append_data(subst, val + ctx->pmatch[i].rm_so,
+ ctx->pmatch[i].rm_eo - ctx->pmatch[i].rm_so);
+ sieve_match_values_add(mvalues, subst);
+ } else
+ skipped++;
+ }
+
+ /* Substitute the new match values */
+ sieve_match_values_commit(mctx->runenv, &mvalues);
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static int mcht_regex_match_keys
+(struct sieve_match_context *mctx, const char *val, size_t val_size ATTR_UNUSED,
+ struct sieve_stringlist *key_list)
+{
+ const struct sieve_runtime_env *renv = mctx->runenv;
+ bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+ struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+ const struct sieve_comparator *cmp = mctx->comparator;
+ int match;
+
+ if ( !ctx->all_compiled ) {
+ string_t *key_item = NULL;
+ unsigned int i;
+ int ret;
+
+ /* Regular expressions still need to be compiled */
+
+ if ( !array_is_created(&ctx->reg_expressions) )
+ p_array_init(&ctx->reg_expressions, mctx->pool, 16);
+
+ i = 0;
+ match = 0;
+ while ( match == 0 &&
+ (ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 ) {
+
+ T_BEGIN {
+ struct mcht_regex_key *rkey;
+
+ if ( i >= array_count(&ctx->reg_expressions) ) {
+ int cflags;
+
+ rkey = array_append_space(&ctx->reg_expressions);
+
+ /* Configure case-sensitivity according to comparator */
+ if ( sieve_comparator_is(cmp, i_octet_comparator) )
+ cflags = REG_EXTENDED;
+ else if ( sieve_comparator_is(cmp, i_ascii_casemap_comparator) )
+ cflags = REG_EXTENDED | REG_ICASE;
+ else
+ rkey->status = -1; /* Not supported */
+
+ if ( rkey->status >= 0 ) {
+ const char *regex_str = str_c(key_item);
+ int rxret;
+
+ /* Indicate whether match values need to be produced */
+ if ( ctx->nmatch == 0 ) cflags |= REG_NOSUB;
+
+ /* Compile regular expression */
+ if ( (rxret=regcomp(&rkey->regexp, regex_str, cflags)) != 0 ) {
+ sieve_runtime_error(renv, NULL,
+ "invalid regular expression '%s' for regex match: %s",
+ str_sanitize(regex_str, 128),
+ _regexp_error(&rkey->regexp, rxret));
+ rkey->status = -1;
+ } else {
+ rkey->status = 1;
+ }
+ }
+ } else {
+ rkey = array_idx_modifiable(&ctx->reg_expressions, 1);
+ }
+
+ if ( rkey->status > 0 ) {
+ match = mcht_regex_match_key(mctx, val, &rkey->regexp);
+
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "with regex `%s' [id=%d] => %d",
+ str_sanitize(str_c(key_item), 80),
+ array_count(&ctx->reg_expressions)-1, match);
+ }
+ }
+ } T_END;
+
+ i++;
+ }
+
+ if ( ret == 0 ) {
+ ctx->all_compiled = TRUE;
+ } else if ( ret < 0 ) {
+ mctx->exec_status = key_list->exec_status;
+ match = -1;
+ }
+
+ } else {
+ const struct mcht_regex_key *rkeys;
+ unsigned int i, count;
+
+ /* Regular expressions are compiled */
+
+ rkeys = array_get(&ctx->reg_expressions, &count);
+
+ i = 0;
+ match = 0;
+ while ( match == 0 && i < count ) {
+ if ( rkeys[i].status > 0 ) {
+ match = mcht_regex_match_key(mctx, val, &rkeys[i].regexp);
+
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "with compiled regex [id=%d] => %d", i, match);
+ }
+ }
+
+ i++;
+ }
+ }
+
+ return match;
+}
+
+void mcht_regex_match_deinit
+(struct sieve_match_context *mctx)
+{
+ struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+ struct mcht_regex_key *rkeys;
+ unsigned int count, i;
+
+ /* Clean up compiled regular expressions */
+ if ( array_is_created(&ctx->reg_expressions) ) {
+ rkeys = array_get_modifiable(&ctx->reg_expressions, &count);
+ for ( i = 0; i < count; i++ ) {
+ regfree(&rkeys[i].regexp);
+ }
+ }
+}
+