/* Sieve variables extension (RFC 5229) Copyright (C) 2016-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 struct sieve_variable { char *value; }; /* FIXME: UTF support */ char * mod_lower (mu_sieve_machine_t mach, char const *value) { char *newval = mu_sieve_malloc (mach, strlen (value) + 1); char *p; for (p = newval; *value; p++, value++) *p = tolower (*value); *p = 0; return newval; } char * mod_upper (mu_sieve_machine_t mach, char const *value) { char *newval = mu_sieve_malloc (mach, strlen (value) + 1); char *p; for (p = newval; *value; p++, value++) *p = toupper (*value); *p = 0; return newval; } char * mod_lowerfirst (mu_sieve_machine_t mach, char const *value) { char *newval = mu_sieve_strdup (mach, value); *newval = tolower (*newval); return newval; } char * mod_upperfirst (mu_sieve_machine_t mach, char const *value) { char *newval = mu_sieve_strdup (mach, value); *newval = toupper (*newval); return newval; } char * mod_quotewildcard (mu_sieve_machine_t mach, char const *value) { size_t len; char *newval; char const *p; char *q; len = 0; for (p = value; *p; p++) { switch (*p) { case '*': case '?': case '\\': len += 2; break; default: len++; } } newval = mu_sieve_malloc (mach, len + 1); for (p = value, q = newval; *p;) { switch (*p) { case '*': case '?': case '\\': *q++ = '\\'; } *q++ = *p++; } *q = 0; return newval; } char * mod_length (mu_sieve_machine_t mach, char const *value) { char *newval, *p; int rc = mu_asprintf (&newval, "%zu", strlen (value)); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_asprintf", NULL, rc); mu_sieve_abort (mach); } p = mu_sieve_strdup (mach, newval); free (newval); return p; } static mu_sieve_tag_def_t set_tags[] = { { "lower", SVT_VOID }, { "upper", SVT_VOID }, { "lowerfirst", SVT_VOID }, { "upperfirst", SVT_VOID }, { "quotewildcard", SVT_VOID }, { "length", SVT_VOID }, { NULL } }; struct modprec { char *name; unsigned prec; char *(*modify) (mu_sieve_machine_t mach, char const *value); }; static struct modprec modprec[] = { { "lower", 40, mod_lower }, { "upper", 40, mod_upper }, { "lowerfirst", 30, mod_lowerfirst }, { "upperfirst", 30, mod_upperfirst }, { "quotewildcard", 20, mod_quotewildcard }, { "length", 10, mod_length }, }; static struct modprec * findprec (char const *name) { int i; for (i = 0; i < MU_ARRAY_SIZE (modprec); i++) if (strcmp (modprec[i].name, name) == 0) return &modprec[i]; mu_error (_("%s:%d: INTERNAL ERROR, please report"), __FILE__, __LINE__); abort (); } static void variable_set (mu_sieve_machine_t mach, char const *name, char *value) { struct sieve_variable *var, **vptr; int rc; rc = mu_assoc_install_ref (mach->vartab, name, &vptr); switch (rc) { case 0: var = malloc (sizeof (*var)); if (!var) { mu_sieve_error (mach, "variable_set: %s", mu_strerror (rc)); mu_sieve_abort (mach); } *vptr = var; break; case MU_ERR_EXISTS: var = *vptr; mu_sieve_free (mach, var->value); break; default: mu_sieve_error (mach, "variable_set: %s", mu_strerror (rc)); mu_sieve_abort (mach); } var->value = value; } static int sieve_action_set (mu_sieve_machine_t mach) { size_t i; char *name; char *value; mu_sieve_get_arg (mach, 0, SVT_STRING, &name); mu_sieve_get_arg (mach, 1, SVT_STRING, &value); value = mu_sieve_strdup (mach, value); for (i = 0; i < mach->tagcount; i++) { mu_sieve_value_t *p = mu_sieve_get_tag_n (mach, i); char *str = findprec (p->tag)->modify (mach, value); mu_sieve_free (mach, value); value = str; } variable_set (mach, name, value); return 0; } static int varini_append (mu_sieve_machine_t mach, struct mu_sieve_variable_initializer *vini) { if (!mu_sieve_has_variables (mach)) return EINVAL; if (!mach->init_var) { mu_list_create (&mach->init_var); mu_list_set_destroy_item (mach->init_var, mu_list_free_item); } return mu_list_append (mach->init_var, vini); } static struct mu_sieve_variable_initializer * varini_alloc (const char *name, const char *value) { struct mu_sieve_variable_initializer *vini; size_t namelen; namelen = strlen (name); vini = malloc (sizeof (*vini) + namelen + strlen (value) + 2); if (vini) { char *p = (char*) (vini + 1); vini->name = p; vini->value = p + namelen + 1; strcpy (vini->name, name); strcpy (vini->value, value); } return vini; } int mu_sieve_variable_initialize (mu_sieve_machine_t mach, char const *name, char const *value) { struct mu_sieve_variable_initializer *vini; int rc; if (!name || !value) return EINVAL; if (!mu_sieve_has_variables (mach)) return EINVAL; vini = varini_alloc (name, value); if (!vini) return ENOMEM; rc = varini_append (mach, vini); if (rc) free (vini); return rc; } static int set_tag_checker (mu_sieve_machine_t mach) { int i, j; /* Sort tags by decreasing priority value (RFC 5229, 4.1) */ for (i = 1; i < mach->tagcount; i++) { mu_sieve_value_t tmp = *mu_sieve_get_tag_n (mach, i); int tmp_prec = findprec (tmp.tag)->prec; for (j = i - 1; j >= 0; j--) { mu_sieve_value_t *t = mu_sieve_get_tag_n (mach, j); int prec = findprec (t->tag)->prec; if (prec < tmp_prec) *mu_sieve_get_tag_n (mach, j + 1) = *t; else if (prec == tmp_prec) { mu_diag_at_locus_range (MU_LOG_ERROR, &t->locus, _("%s and %s can't be used together"), tmp.tag, t->tag); mu_diag_at_locus_range (MU_LOG_ERROR, &tmp.locus, _("%s encountered here"), tmp.tag); mu_i_sv_error (mach); return 1; } else break; } *mu_sieve_get_tag_n (mach, j + 1) = tmp; } return 0; } static mu_sieve_tag_group_t set_tag_groups[] = { { set_tags, set_tag_checker }, { NULL } }; static mu_sieve_data_type set_args[] = { SVT_STRING, SVT_STRING, SVT_VOID }; static int retrieve_string (void *item, void *data, size_t idx, char **pval) { if (idx) return MU_ERR_NOENT; *pval = strdup ((char*)item); if (!*pval) return errno; return 0; } int fold_string (void *item, void *data, void *prev, void **ret) { char *str = item; size_t count = *(size_t*) prev; /* The "relational" extension [RELATIONAL] adds a match type called ":count". The count of a single string is 0 if it is the empty string, or 1 otherwise. The count of a string list is the sum of the counts of the member strings. */ if (*str) ++count; *(size_t*)ret = count; return 0; } /* RFC 5229, 5. Test string */ int sieve_test_string (mu_sieve_machine_t mach) { mu_sieve_value_t *source, *key_list; source = mu_sieve_get_arg_untyped (mach, 0); key_list = mu_sieve_get_arg_untyped (mach, 1); return mu_sieve_vlist_compare (mach, source, key_list, retrieve_string, fold_string, mach); } static mu_sieve_data_type string_args[] = { SVT_STRING_LIST, SVT_STRING_LIST, SVT_VOID }; static mu_sieve_tag_group_t string_tag_groups[] = { { mu_sieve_match_part_tags, mu_sieve_match_part_checker }, { NULL } }; int mu_i_sv_expand_variables (char const *input, size_t len, char **exp, void *data) { mu_sieve_machine_t mach = data; if (mu_isdigit (*input)) { char *p; size_t idx = 0; while (len) { if (mu_isdigit (*input)) { int d = *input - '0'; idx = idx * 10 + d; input++; len--; } else return 1; } if (idx > mach->match_count) { *exp = NULL; return 0; } len = mach->match_buf[idx].rm_eo - mach->match_buf[idx].rm_so; p = malloc (len + 1); if (!p) { mu_sieve_error (mach, "%s", mu_strerror (errno)); mu_sieve_abort (mach); } memcpy (p, mach->match_string + mach->match_buf[idx].rm_so, len); p[len] = 0; *exp = p; } else if (mu_isalpha (*input)) { size_t i; char *name; struct sieve_variable *var; for (i = 0; i < len; i++) if (!(mu_isalnum (input[i]) || input[i] == '_')) return 1; name = malloc (len + 1); if (!name) { mu_sieve_error (mach, "%s", mu_strerror (errno)); mu_sieve_abort (mach); } memcpy (name, input, len); name[len] = 0; var = mu_assoc_get (mach->vartab, name); free (name); if (var) { *exp = strdup (var->value); if (!*exp) { mu_sieve_error (mach, "%s", mu_strerror (errno)); mu_sieve_abort (mach); } } else *exp = NULL; } else return 1; return 0; } int mu_sieve_require_variables (mu_sieve_machine_t mach) { int rc; if (mach->vartab) return 0; rc = mu_assoc_create (&mach->vartab, MU_ASSOC_ICASE); if (rc) mu_sieve_error (mach, "mu_assoc_create: %s", mu_strerror (rc)); mu_assoc_set_destroy_item (mach->vartab, mu_list_free_item); if (rc == 0) { mu_sieve_register_action (mach, "set", sieve_action_set, set_args, set_tag_groups, 1); mu_sieve_register_test (mach, "string", sieve_test_string, string_args, string_tag_groups, 1); } return rc; } int mu_sieve_has_variables (mu_sieve_machine_t mach) { return mach->vartab != NULL; } static int copy_init_var (void *item, void *data) { struct mu_sieve_variable_initializer *vini = item, *vini_new; mu_sieve_machine_t mach = data; vini_new = varini_alloc (vini->name, vini->value); if (!vini_new) return ENOMEM; return varini_append (mach, vini_new); } void mu_i_sv_copy_variables (mu_sieve_machine_t child, mu_sieve_machine_t parent) { mu_iterator_t itr; int rc; mu_sieve_require_variables (child); rc = mu_assoc_get_iterator (parent->vartab, &itr); if (rc) { mu_sieve_error (child, "mu_assoc_get_iterator: %s", mu_strerror (rc)); mu_sieve_abort (child); } for (mu_iterator_first (itr); !mu_iterator_is_done (itr); mu_iterator_next (itr)) { const char *name; struct sieve_variable const *val; struct sieve_variable *newval; mu_iterator_current_kv (itr, (const void **)&name, (void**)&val); newval = malloc (sizeof (*newval)); if (!newval) mu_sieve_abort (child); newval->value = mu_sieve_strdup (child, val->value); mu_assoc_install (child->vartab, name, newval); } mu_iterator_destroy (&itr); rc = mu_list_foreach (parent->init_var, copy_init_var, child); if (rc) { mu_sieve_error (child, "copy_init_var: %s", mu_strerror (rc)); mu_sieve_abort (child); } } static int sieve_setvar (void *item, void *data) { struct mu_sieve_variable_initializer *vini = item; mu_sieve_machine_t mach = data; variable_set (mach, vini->name, mu_sieve_strdup (mach, vini->value)); return 0; } void mu_i_sv_init_variables (mu_sieve_machine_t mach) { if (mu_sieve_has_variables (mach)) { mu_assoc_clear (mach->vartab); mu_list_foreach (mach->init_var, sieve_setvar, mach); } }