1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2003-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils is free software; you can redistribute it and/or modify
5    it under the terms of the GNU Lesser General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Mailutils 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
12    GNU Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with GNU Mailutils.  If not, see
16    <http://www.gnu.org/licenses/>. */
17 
18 /* Implements "list" sieve extension test. See "Syntax:" below for the
19    description */
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <mailutils/sieve.h>
29 
30 
31 
32 /* Auxiliary functions */
33 struct header_closure
34 {
35   mu_header_t header;     /* Message header */
36   int index;              /* Header index */
37   char *delim;            /* List delimiter */
38   char **valv;            /* Retrieved and split-out header values */
39   size_t valc;            /* Number of values in valv */
40   size_t vali;            /* Current index in valv */
41 };
42 
43 static void
cleanup(struct header_closure * hc)44 cleanup (struct header_closure *hc)
45 {
46   mu_argcv_free (hc->valc, hc->valv);
47   hc->valv = NULL;
48   hc->valc = hc->vali = 0;
49 }
50 
51 static int
retrieve_next_header(struct header_closure * hc,char * name,char ** pval)52 retrieve_next_header (struct header_closure *hc, char *name, char **pval)
53 {
54   const char *buf;
55 
56   cleanup (hc);
57   while (!mu_header_sget_field_name (hc->header, hc->index, &buf))
58     {
59       int i = hc->index++;
60       if (mu_c_strcasecmp (buf, name) == 0)
61 	{
62 	  const char *value;
63 	  struct mu_wordsplit ws;
64 
65 	  if (mu_header_sget_field_value (hc->header, i, &value))
66 	    return 1;
67 	  ws.ws_delim = hc->delim;
68 	  if (mu_wordsplit (value, &ws,
69 			    MU_WRDSF_DELIM|MU_WRDSF_SQUEEZE_DELIMS|
70 			    MU_WRDSF_WS|
71 			    MU_WRDSF_NOVAR|MU_WRDSF_NOCMD))
72 	    {
73 	      mu_error (_("cannot split line `%s': %s"), value,
74 			mu_wordsplit_strerror (&ws));
75 	      return 1;
76 	    }
77 	  if (ws.ws_wordc == 0)
78 	    {
79 	      cleanup (hc);
80 	      mu_wordsplit_free (&ws);
81 	      return 1;
82 	    }
83 	  mu_wordsplit_get_words (&ws, &hc->valc, &hc->valv);
84 	  mu_wordsplit_free (&ws);
85 	  hc->vali = 0;
86 	  *pval = hc->valv[hc->vali++];
87 	  return 0;
88 	}
89     }
90 
91   return 1;
92 }
93 
94 static int
list_retrieve_header(void * item,void * data,size_t idx,char ** pval)95 list_retrieve_header (void *item, void *data, size_t idx, char **pval)
96 {
97   struct header_closure *hc = data;
98   char *p;
99 
100   if (idx == 0)
101     hc->index = 1;
102 
103   while (1)
104     {
105       if (!hc->valv)
106 	{
107 	  if (retrieve_next_header (hc, (char*) item, &p))
108 	    return MU_ERR_NOENT;
109 	}
110       else if (hc->vali == hc->valc)
111 	{
112 	  cleanup (hc);
113 	  continue;
114 	}
115       else
116 	p = hc->valv[hc->vali++];
117 
118       if ((*pval = strdup (p)) == NULL)
119         return errno;
120       return 0;
121     }
122 
123   return MU_ERR_NOENT;
124 }
125 
126 
127 /* The test proper */
128 
129 /* Syntax: list [COMPARATOR] [MATCH-TYPE]
130                 [ :delim <delimiters: string> ]
131                 <headers: string-list> <key-list: string-list>
132 
133    The "list" test evaluates to true if any of the headers
134    match any key. Each header is regarded as containing a
135    list of keywords. By default, comma is assumed as list
136    separator. This can be overridden by specifying ":delim"
137    tag, whose value is a string consisting of valid list
138    delimiter characters.
139 
140    list :matches :delim " ," [ "X-Spam-Keywords", "X-Spamd-Keywords" ]
141              [ "HTML_*", "FORGED_*" ]
142 
143 
144 */
145 
146 static int
list_test(mu_sieve_machine_t mach)147 list_test (mu_sieve_machine_t mach)
148 {
149   mu_sieve_value_t *h, *v;
150   struct header_closure clos;
151   int result;
152 
153   memset (&clos, 0, sizeof clos);
154   if (!mu_sieve_get_tag (mach, "delim", SVT_STRING, &clos.delim))
155     clos.delim = ",";
156 
157   h = mu_sieve_get_arg_untyped (mach, 0);
158   v = mu_sieve_get_arg_untyped (mach, 1);
159   mu_message_get_header (mu_sieve_get_message (mach), &clos.header);
160   result = mu_sieve_vlist_compare (mach, h, v, list_retrieve_header, NULL,
161 				   &clos);
162   cleanup (&clos);
163   return result;
164 }
165 
166 
167 /* Initialization */
168 
169 /* Required arguments: */
170 static mu_sieve_data_type list_req_args[] = {
171   SVT_STRING_LIST,
172   SVT_STRING_LIST,
173   SVT_VOID
174 };
175 
176 static mu_sieve_tag_def_t match_part_tags[] = {
177   { "is", SVT_VOID },
178   { "contains", SVT_VOID },
179   { "matches", SVT_VOID },
180   { "regex", SVT_VOID },
181   { "count", SVT_STRING },
182   { "value", SVT_STRING },
183   { "comparator", SVT_STRING },
184   { NULL }
185 };
186 
187 static mu_sieve_tag_def_t delim_part_tags[] = {
188   { "delim", SVT_STRING },
189   { NULL }
190 };
191 
192 static mu_sieve_tag_group_t list_tag_groups[] = {
193   { match_part_tags, mu_sieve_match_part_checker },
194   { delim_part_tags, NULL },
195   { NULL }
196 };
197 
198 /* Initialization function. */
199 int
SIEVE_EXPORT(list,init)200 SIEVE_EXPORT(list,init) (mu_sieve_machine_t mach)
201 {
202   mu_sieve_register_test (mach, "list", list_test,
203 			  list_req_args, list_tag_groups, 1);
204   return 0;
205 }
206 
207 /* End of list.c */
208