/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999-2021 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
. */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
void
mu_sieve_register_comparator (mu_sieve_machine_t mach,
const char *name,
int required,
mu_sieve_comparator_t is,
mu_sieve_comparator_t contains,
mu_sieve_comparator_t matches,
mu_sieve_comparator_t regex,
mu_sieve_comparator_t eq)
{
mu_sieve_registry_t *reg = mu_sieve_registry_add (mach, name);
reg->type = mu_sieve_record_comparator;
reg->required = required;
reg->name = name;
reg->v.comp[MU_SIEVE_MATCH_IS] = is;
reg->v.comp[MU_SIEVE_MATCH_CONTAINS] = contains;
reg->v.comp[MU_SIEVE_MATCH_MATCHES] = matches;
reg->v.comp[MU_SIEVE_MATCH_REGEX] = regex;
reg->v.comp[MU_SIEVE_MATCH_EQ] = eq;
}
mu_sieve_comparator_t
mu_sieve_comparator_lookup (mu_sieve_machine_t mach, const char *name,
int matchtype)
{
mu_sieve_registry_t *reg =
mu_sieve_registry_lookup (mach, name, mu_sieve_record_comparator);
if (reg && reg->v.comp[matchtype])
return reg->v.comp[matchtype];
return NULL;
}
static int i_ascii_casemap_is (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text);
mu_sieve_comparator_t
mu_sieve_get_comparator (mu_sieve_machine_t mach)
{
if (!mach->comparator)
return i_ascii_casemap_is;
return mach->comparator;
}
/* Compile time support */
static void
compile_pattern (mu_sieve_machine_t mach, mu_sieve_string_t *pattern, int flags)
{
int rc;
regex_t *preg;
char *str;
str = mu_sieve_string_get (mach, pattern);
if (pattern->rx)
{
if (!pattern->changed)
return;
preg = pattern->rx;
regfree (preg);
}
else
preg = mu_sieve_malloc (mach, sizeof (*preg));
rc = regcomp (preg, str, REG_EXTENDED | flags);
if (rc)
{
size_t size = regerror (rc, preg, NULL, 0);
char *errbuf = malloc (size + 1);
if (errbuf)
{
regerror (rc, preg, errbuf, size);
mu_sieve_error (mach, _("regex error: %s"), errbuf);
free (errbuf);
}
else
mu_sieve_error (mach, _("regex error"));
mu_sieve_abort (mach);
}
pattern->rx = preg;
}
static void
compile_wildcard (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
int flags)
{
int rc;
regex_t *preg;
char *str;
str = mu_sieve_string_get (mach, pattern);
if (pattern->rx)
{
if (!pattern->changed)
return;
preg = pattern->rx;
regfree (preg);
}
else
preg = mu_sieve_malloc (mach, sizeof (*preg));
if (mu_sieve_has_variables (mach))
flags |= MU_GLOBF_SUB;
rc = mu_glob_compile (preg, str, flags);
if (rc)
{
mu_sieve_error (mach, _("can't compile pattern"));
mu_sieve_abort (mach);
}
pattern->rx = preg;
}
static int
comp_false (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
return 0;
}
int
mu_sieve_match_part_checker (mu_sieve_machine_t mach)
{
size_t i;
mu_sieve_value_t *match = NULL;
mu_sieve_comparator_t compfun = NULL;
char *compname = NULL;
int matchtype;
if (mach->tagcount == 0)
return 0;
for (i = 0; i < mach->tagcount; i++)
{
mu_sieve_value_t *t = mu_sieve_get_tag_n (mach, i);
if (strcmp (t->tag, "is") == 0
|| strcmp (t->tag, "contains") == 0
|| strcmp (t->tag, "matches") == 0
|| strcmp (t->tag, "regex") == 0
|| strcmp (t->tag, "count") == 0
|| strcmp (t->tag, "value") == 0)
{
if (match)
{
mu_diag_at_locus_range (MU_LOG_ERROR, &t->locus,
_("match type specified twice in call to `%s'"),
mach->identifier);
mu_i_sv_error (mach);
return 1;
}
else
match = t;
}
else if (strcmp (t->tag, "comparator") == 0)
{
if (t->type != SVT_STRING)
abort ();
compname = mu_sieve_string (mach, &t->v.list, 0);
}
}
if (!match || strcmp (match->tag, "is") == 0)
matchtype = MU_SIEVE_MATCH_IS;
else if (strcmp (match->tag, "contains") == 0)
matchtype = MU_SIEVE_MATCH_CONTAINS;
else if (strcmp (match->tag, "matches") == 0)
matchtype = MU_SIEVE_MATCH_MATCHES;
else if (strcmp (match->tag, "regex") == 0)
matchtype = MU_SIEVE_MATCH_REGEX;
else if (match->type == SVT_STRING)
{
char *str = mu_sieve_string (mach, &match->v.list, 0);
if (strcmp (match->tag, "count") == 0)
{
mu_sieve_value_t *val;
mu_sieve_string_t *argstr;
if (compname && strcmp (compname, "i;ascii-numeric"))
{
mu_diag_at_locus_range (MU_LOG_ERROR, &match->locus,
/* TRANSLATORS: Do not translate ':count'.
It is the name of a Sieve tag */
_("comparator %s is incompatible with "
":count in call to `%s'"),
compname,
mach->identifier);
mu_i_sv_error (mach);
return 1;
}
matchtype = MU_SIEVE_MATCH_LAST; /* to not leave it undefined */
compname = "false";
compfun = comp_false;
val = mu_sieve_get_arg_untyped (mach, 1);
/* NOTE: Type of val is always SVT_STRING_LIST */
switch (val->type)
{
case SVT_STRING:
break;
case SVT_STRING_LIST:
if (val->v.list.count == 1)
break;
/* fall through */
default:
mu_diag_at_locus_range (MU_LOG_ERROR, &val->locus,
_(":count requires second argument to be a list of one element"));
mu_i_sv_error (mach);
return 1;
}
argstr = mu_sieve_string_raw (mach, &val->v.list, 0);
if (argstr->constant)
{
char *p = mu_str_skip_class (argstr->orig, MU_CTYPE_DIGIT);
if (*p)
{
mu_diag_at_locus_range (MU_LOG_ERROR, &val->locus,
_("second argument cannot be converted to number"));
mu_i_sv_error (mach);
return 1;
}
}
}
else
matchtype = MU_SIEVE_MATCH_EQ;
if (mu_sieve_str_to_relcmp (str, NULL, NULL))
{
mu_diag_at_locus_range (MU_LOG_ERROR, &match->locus,
_("invalid relational match `%s' in call to `%s'"),
str, mach->identifier);
mu_i_sv_error (mach);
return 1;
}
}
else
{
mu_error (_("%s:%d: INTERNAL ERROR, please report"), __FILE__, __LINE__);
abort ();
}
if (!compfun)
{
if (!compname)
compname = "i;ascii-casemap";
compfun = mu_sieve_comparator_lookup (mach, compname, matchtype);
if (!compfun)
{
if (match)
mu_diag_at_locus_range (MU_LOG_ERROR, &match->locus,
_("comparator `%s' is incompatible with match type `%s' in call to `%s'"),
compname, match->tag,
mach->identifier);
else
mu_diag_at_locus_range (MU_LOG_ERROR, &mach->locus,
_("comparator `%s' is incompatible with match type `%s' in call to `%s'"),
compname, "is",
mach->identifier);
mu_i_sv_error (mach);
return 1;
}
}
mach->comparator = compfun;
return 0;
}
static int
regmatch (mu_sieve_machine_t mach, mu_sieve_string_t *pattern, char const *text)
{
regex_t *reg = pattern->rx;
regmatch_t *match_buf = NULL;
size_t match_count = 0;
if (mu_sieve_has_variables (mach))
{
match_count = reg->re_nsub + 1;
while (mach->match_max < match_count)
mu_i_sv_2nrealloc (mach, (void **) &mach->match_buf,
&mach->match_max,
sizeof (mach->match_buf[0]));
mach->match_count = match_count;
mu_sieve_free (mach, mach->match_string);
mach->match_string = mu_sieve_strdup (mach, text);
match_buf = mach->match_buf;
}
return regexec (reg, text, match_count, match_buf, 0) == 0;
}
/* Particular comparators */
/* :comparator i;octet */
static int
i_octet_is (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
return strcmp (mu_sieve_string_get (mach, pattern), text) == 0;
}
static int
i_octet_contains (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
return strstr (text, mu_sieve_string_get (mach, pattern)) != NULL;
}
static int
i_octet_matches (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
compile_wildcard (mach, pattern, 0);
return regmatch (mach, pattern, text);
}
static int
i_octet_regex (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
compile_pattern (mach, pattern, 0);
return regmatch (mach, pattern, text);
}
static int
i_octet_eq (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
return strcmp (text, mu_sieve_string_get (mach, pattern));
}
/* :comparator i;ascii-casemap */
static int
i_ascii_casemap_is (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
return mu_c_strcasecmp (mu_sieve_string_get (mach, pattern), text) == 0;
}
static int
i_ascii_casemap_contains (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
return mu_c_strcasestr (text, mu_sieve_string_get (mach, pattern)) != NULL;
}
static int
i_ascii_casemap_matches (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
compile_wildcard (mach, pattern, MU_GLOBF_ICASE);
return regmatch (mach, pattern, text);
}
static int
i_ascii_casemap_regex (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
compile_pattern (mach, pattern, REG_ICASE);
return regmatch (mach, pattern, text);
}
static int
i_ascii_casemap_eq (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
return mu_c_strcasecmp (text, mu_sieve_string_get (mach, pattern));
}
/* :comparator i;ascii-numeric */
static int
i_ascii_numeric_is (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
char *str = mu_sieve_string_get (mach, pattern);
if (mu_isdigit (*str))
{
if (mu_isdigit (*text))
/* FIXME: Error checking */
return strtol (str, NULL, 10) == strtol (text, NULL, 10);
else
return 0;
}
else if (mu_isdigit (*text))
return 0;
else
return 1;
}
static int
i_ascii_numeric_eq (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
char *str = mu_sieve_string_get (mach, pattern);
if (mu_isdigit (*str))
{
if (mu_isdigit (*text))
{
size_t a = strtoul (str, NULL, 10);
size_t b = strtoul (text, NULL, 10);
if (b > a)
return 1;
else if (b < a)
return -1;
return 0;
}
else
return 1;
}
else
return 1;
}
void
mu_i_sv_register_standard_comparators (mu_sieve_machine_t mach)
{
mu_sieve_register_comparator (mach,
"i;octet",
1,
i_octet_is,
i_octet_contains,
i_octet_matches,
i_octet_regex,
i_octet_eq);
mu_sieve_register_comparator (mach,
"i;ascii-casemap",
1,
i_ascii_casemap_is,
i_ascii_casemap_contains,
i_ascii_casemap_matches,
i_ascii_casemap_regex,
i_ascii_casemap_eq);
mu_sieve_register_comparator (mach,
"i;ascii-numeric",
0,
i_ascii_numeric_is,
NULL,
NULL,
NULL,
i_ascii_numeric_eq);
}