1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 1999-2021 Free Software Foundation, Inc.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 3 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General
15 Public License along with this library. If not, see
16 <http://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <sieve-priv.h>
27 #include <regex.h>
28 #include <mailutils/cctype.h>
29 #include <mailutils/cstr.h>
30
31 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)32 mu_sieve_register_comparator (mu_sieve_machine_t mach,
33 const char *name,
34 int required,
35 mu_sieve_comparator_t is,
36 mu_sieve_comparator_t contains,
37 mu_sieve_comparator_t matches,
38 mu_sieve_comparator_t regex,
39 mu_sieve_comparator_t eq)
40 {
41 mu_sieve_registry_t *reg = mu_sieve_registry_add (mach, name);
42
43 reg->type = mu_sieve_record_comparator;
44 reg->required = required;
45 reg->name = name;
46 reg->v.comp[MU_SIEVE_MATCH_IS] = is;
47 reg->v.comp[MU_SIEVE_MATCH_CONTAINS] = contains;
48 reg->v.comp[MU_SIEVE_MATCH_MATCHES] = matches;
49 reg->v.comp[MU_SIEVE_MATCH_REGEX] = regex;
50 reg->v.comp[MU_SIEVE_MATCH_EQ] = eq;
51 }
52
53 mu_sieve_comparator_t
mu_sieve_comparator_lookup(mu_sieve_machine_t mach,const char * name,int matchtype)54 mu_sieve_comparator_lookup (mu_sieve_machine_t mach, const char *name,
55 int matchtype)
56 {
57 mu_sieve_registry_t *reg =
58 mu_sieve_registry_lookup (mach, name, mu_sieve_record_comparator);
59 if (reg && reg->v.comp[matchtype])
60 return reg->v.comp[matchtype];
61 return NULL;
62 }
63
64 static int i_ascii_casemap_is (mu_sieve_machine_t mach,
65 mu_sieve_string_t *pattern, const char *text);
66
67 mu_sieve_comparator_t
mu_sieve_get_comparator(mu_sieve_machine_t mach)68 mu_sieve_get_comparator (mu_sieve_machine_t mach)
69 {
70 if (!mach->comparator)
71 return i_ascii_casemap_is;
72 return mach->comparator;
73 }
74
75 /* Compile time support */
76 static void
compile_pattern(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,int flags)77 compile_pattern (mu_sieve_machine_t mach, mu_sieve_string_t *pattern, int flags)
78 {
79 int rc;
80 regex_t *preg;
81 char *str;
82
83 str = mu_sieve_string_get (mach, pattern);
84
85 if (pattern->rx)
86 {
87 if (!pattern->changed)
88 return;
89 preg = pattern->rx;
90 regfree (preg);
91 }
92 else
93 preg = mu_sieve_malloc (mach, sizeof (*preg));
94 rc = regcomp (preg, str, REG_EXTENDED | flags);
95 if (rc)
96 {
97 size_t size = regerror (rc, preg, NULL, 0);
98 char *errbuf = malloc (size + 1);
99 if (errbuf)
100 {
101 regerror (rc, preg, errbuf, size);
102 mu_sieve_error (mach, _("regex error: %s"), errbuf);
103 free (errbuf);
104 }
105 else
106 mu_sieve_error (mach, _("regex error"));
107 mu_sieve_abort (mach);
108 }
109 pattern->rx = preg;
110 }
111
112 static void
compile_wildcard(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,int flags)113 compile_wildcard (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
114 int flags)
115 {
116 int rc;
117 regex_t *preg;
118 char *str;
119
120 str = mu_sieve_string_get (mach, pattern);
121
122 if (pattern->rx)
123 {
124 if (!pattern->changed)
125 return;
126 preg = pattern->rx;
127 regfree (preg);
128 }
129 else
130 preg = mu_sieve_malloc (mach, sizeof (*preg));
131
132 if (mu_sieve_has_variables (mach))
133 flags |= MU_GLOBF_SUB;
134 rc = mu_glob_compile (preg, str, flags);
135 if (rc)
136 {
137 mu_sieve_error (mach, _("can't compile pattern"));
138 mu_sieve_abort (mach);
139 }
140 pattern->rx = preg;
141 }
142
143 static int
comp_false(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)144 comp_false (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
145 const char *text)
146 {
147 return 0;
148 }
149
150 int
mu_sieve_match_part_checker(mu_sieve_machine_t mach)151 mu_sieve_match_part_checker (mu_sieve_machine_t mach)
152 {
153 size_t i;
154 mu_sieve_value_t *match = NULL;
155 mu_sieve_comparator_t compfun = NULL;
156 char *compname = NULL;
157
158 int matchtype;
159
160 if (mach->tagcount == 0)
161 return 0;
162
163 for (i = 0; i < mach->tagcount; i++)
164 {
165 mu_sieve_value_t *t = mu_sieve_get_tag_n (mach, i);
166
167 if (strcmp (t->tag, "is") == 0
168 || strcmp (t->tag, "contains") == 0
169 || strcmp (t->tag, "matches") == 0
170 || strcmp (t->tag, "regex") == 0
171 || strcmp (t->tag, "count") == 0
172 || strcmp (t->tag, "value") == 0)
173 {
174 if (match)
175 {
176 mu_diag_at_locus_range (MU_LOG_ERROR, &t->locus,
177 _("match type specified twice in call to `%s'"),
178 mach->identifier);
179 mu_i_sv_error (mach);
180 return 1;
181 }
182 else
183 match = t;
184 }
185 else if (strcmp (t->tag, "comparator") == 0)
186 {
187 if (t->type != SVT_STRING)
188 abort ();
189 compname = mu_sieve_string (mach, &t->v.list, 0);
190 }
191 }
192
193 if (!match || strcmp (match->tag, "is") == 0)
194 matchtype = MU_SIEVE_MATCH_IS;
195 else if (strcmp (match->tag, "contains") == 0)
196 matchtype = MU_SIEVE_MATCH_CONTAINS;
197 else if (strcmp (match->tag, "matches") == 0)
198 matchtype = MU_SIEVE_MATCH_MATCHES;
199 else if (strcmp (match->tag, "regex") == 0)
200 matchtype = MU_SIEVE_MATCH_REGEX;
201 else if (match->type == SVT_STRING)
202 {
203 char *str = mu_sieve_string (mach, &match->v.list, 0);
204 if (strcmp (match->tag, "count") == 0)
205 {
206 mu_sieve_value_t *val;
207 mu_sieve_string_t *argstr;
208
209 if (compname && strcmp (compname, "i;ascii-numeric"))
210 {
211 mu_diag_at_locus_range (MU_LOG_ERROR, &match->locus,
212 /* TRANSLATORS: Do not translate ':count'.
213 It is the name of a Sieve tag */
214 _("comparator %s is incompatible with "
215 ":count in call to `%s'"),
216 compname,
217 mach->identifier);
218 mu_i_sv_error (mach);
219 return 1;
220 }
221
222 matchtype = MU_SIEVE_MATCH_LAST; /* to not leave it undefined */
223 compname = "false";
224 compfun = comp_false;
225 val = mu_sieve_get_arg_untyped (mach, 1);
226 /* NOTE: Type of val is always SVT_STRING_LIST */
227 switch (val->type)
228 {
229 case SVT_STRING:
230 break;
231
232 case SVT_STRING_LIST:
233 if (val->v.list.count == 1)
234 break;
235 /* fall through */
236 default:
237 mu_diag_at_locus_range (MU_LOG_ERROR, &val->locus,
238 _(":count requires second argument to be a list of one element"));
239 mu_i_sv_error (mach);
240 return 1;
241 }
242 argstr = mu_sieve_string_raw (mach, &val->v.list, 0);
243 if (argstr->constant)
244 {
245 char *p = mu_str_skip_class (argstr->orig, MU_CTYPE_DIGIT);
246 if (*p)
247 {
248 mu_diag_at_locus_range (MU_LOG_ERROR, &val->locus,
249 _("second argument cannot be converted to number"));
250 mu_i_sv_error (mach);
251 return 1;
252 }
253 }
254 }
255 else
256 matchtype = MU_SIEVE_MATCH_EQ;
257
258 if (mu_sieve_str_to_relcmp (str, NULL, NULL))
259 {
260 mu_diag_at_locus_range (MU_LOG_ERROR, &match->locus,
261 _("invalid relational match `%s' in call to `%s'"),
262 str, mach->identifier);
263 mu_i_sv_error (mach);
264 return 1;
265 }
266 }
267 else
268 {
269 mu_error (_("%s:%d: INTERNAL ERROR, please report"), __FILE__, __LINE__);
270 abort ();
271 }
272
273 if (!compfun)
274 {
275 if (!compname)
276 compname = "i;ascii-casemap";
277 compfun = mu_sieve_comparator_lookup (mach, compname, matchtype);
278 if (!compfun)
279 {
280 if (match)
281 mu_diag_at_locus_range (MU_LOG_ERROR, &match->locus,
282 _("comparator `%s' is incompatible with match type `%s' in call to `%s'"),
283 compname, match->tag,
284 mach->identifier);
285 else
286 mu_diag_at_locus_range (MU_LOG_ERROR, &mach->locus,
287 _("comparator `%s' is incompatible with match type `%s' in call to `%s'"),
288 compname, "is",
289 mach->identifier);
290 mu_i_sv_error (mach);
291 return 1;
292 }
293 }
294
295 mach->comparator = compfun;
296
297 return 0;
298 }
299
300 static int
regmatch(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,char const * text)301 regmatch (mu_sieve_machine_t mach, mu_sieve_string_t *pattern, char const *text)
302 {
303 regex_t *reg = pattern->rx;
304 regmatch_t *match_buf = NULL;
305 size_t match_count = 0;
306
307 if (mu_sieve_has_variables (mach))
308 {
309 match_count = reg->re_nsub + 1;
310 while (mach->match_max < match_count)
311 mu_i_sv_2nrealloc (mach, (void **) &mach->match_buf,
312 &mach->match_max,
313 sizeof (mach->match_buf[0]));
314 mach->match_count = match_count;
315 mu_sieve_free (mach, mach->match_string);
316 mach->match_string = mu_sieve_strdup (mach, text);
317
318 match_buf = mach->match_buf;
319 }
320
321 return regexec (reg, text, match_count, match_buf, 0) == 0;
322 }
323 /* Particular comparators */
324
325 /* :comparator i;octet */
326
327 static int
i_octet_is(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)328 i_octet_is (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
329 const char *text)
330 {
331 return strcmp (mu_sieve_string_get (mach, pattern), text) == 0;
332 }
333
334 static int
i_octet_contains(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)335 i_octet_contains (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
336 const char *text)
337 {
338 return strstr (text, mu_sieve_string_get (mach, pattern)) != NULL;
339 }
340
341 static int
i_octet_matches(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)342 i_octet_matches (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
343 const char *text)
344 {
345 compile_wildcard (mach, pattern, 0);
346 return regmatch (mach, pattern, text);
347 }
348
349 static int
i_octet_regex(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)350 i_octet_regex (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
351 const char *text)
352 {
353 compile_pattern (mach, pattern, 0);
354 return regmatch (mach, pattern, text);
355 }
356
357 static int
i_octet_eq(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)358 i_octet_eq (mu_sieve_machine_t mach,
359 mu_sieve_string_t *pattern, const char *text)
360 {
361 return strcmp (text, mu_sieve_string_get (mach, pattern));
362 }
363
364 /* :comparator i;ascii-casemap */
365 static int
i_ascii_casemap_is(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)366 i_ascii_casemap_is (mu_sieve_machine_t mach,
367 mu_sieve_string_t *pattern, const char *text)
368 {
369 return mu_c_strcasecmp (mu_sieve_string_get (mach, pattern), text) == 0;
370 }
371
372 static int
i_ascii_casemap_contains(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)373 i_ascii_casemap_contains (mu_sieve_machine_t mach,
374 mu_sieve_string_t *pattern, const char *text)
375 {
376 return mu_c_strcasestr (text, mu_sieve_string_get (mach, pattern)) != NULL;
377 }
378
379 static int
i_ascii_casemap_matches(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)380 i_ascii_casemap_matches (mu_sieve_machine_t mach,
381 mu_sieve_string_t *pattern, const char *text)
382 {
383 compile_wildcard (mach, pattern, MU_GLOBF_ICASE);
384 return regmatch (mach, pattern, text);
385 }
386
387 static int
i_ascii_casemap_regex(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)388 i_ascii_casemap_regex (mu_sieve_machine_t mach,
389 mu_sieve_string_t *pattern, const char *text)
390 {
391 compile_pattern (mach, pattern, REG_ICASE);
392 return regmatch (mach, pattern, text);
393 }
394
395 static int
i_ascii_casemap_eq(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)396 i_ascii_casemap_eq (mu_sieve_machine_t mach,
397 mu_sieve_string_t *pattern, const char *text)
398 {
399 return mu_c_strcasecmp (text, mu_sieve_string_get (mach, pattern));
400 }
401
402 /* :comparator i;ascii-numeric */
403 static int
i_ascii_numeric_is(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)404 i_ascii_numeric_is (mu_sieve_machine_t mach,
405 mu_sieve_string_t *pattern, const char *text)
406 {
407 char *str = mu_sieve_string_get (mach, pattern);
408 if (mu_isdigit (*str))
409 {
410 if (mu_isdigit (*text))
411 /* FIXME: Error checking */
412 return strtol (str, NULL, 10) == strtol (text, NULL, 10);
413 else
414 return 0;
415 }
416 else if (mu_isdigit (*text))
417 return 0;
418 else
419 return 1;
420 }
421
422 static int
i_ascii_numeric_eq(mu_sieve_machine_t mach,mu_sieve_string_t * pattern,const char * text)423 i_ascii_numeric_eq (mu_sieve_machine_t mach,
424 mu_sieve_string_t *pattern, const char *text)
425 {
426 char *str = mu_sieve_string_get (mach, pattern);
427 if (mu_isdigit (*str))
428 {
429 if (mu_isdigit (*text))
430 {
431 size_t a = strtoul (str, NULL, 10);
432 size_t b = strtoul (text, NULL, 10);
433 if (b > a)
434 return 1;
435 else if (b < a)
436 return -1;
437 return 0;
438 }
439 else
440 return 1;
441 }
442 else
443 return 1;
444 }
445
446 void
mu_i_sv_register_standard_comparators(mu_sieve_machine_t mach)447 mu_i_sv_register_standard_comparators (mu_sieve_machine_t mach)
448 {
449 mu_sieve_register_comparator (mach,
450 "i;octet",
451 1,
452 i_octet_is,
453 i_octet_contains,
454 i_octet_matches,
455 i_octet_regex,
456 i_octet_eq);
457 mu_sieve_register_comparator (mach,
458 "i;ascii-casemap",
459 1,
460 i_ascii_casemap_is,
461 i_ascii_casemap_contains,
462 i_ascii_casemap_matches,
463 i_ascii_casemap_regex,
464 i_ascii_casemap_eq);
465 mu_sieve_register_comparator (mach,
466 "i;ascii-numeric",
467 0,
468 i_ascii_numeric_is,
469 NULL,
470 NULL,
471 NULL,
472 i_ascii_numeric_eq);
473 }
474