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