1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 2006-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 /* Moderator robot for Mailman-driven mail archives.
19 Mailman moderation request is a MIME message consisting of the three parts:
20
21 1 text/plain Introduction for the human reader.
22 2 message/rfc822 Original submission.
23 3 message/rfc822 Mailman control message.
24
25 Replying to part 3 (keeping the subject intact) instructs Mailman to
26 discard the original submission.
27 Replying to part 3 while adding an `Approved:' header with the list
28 password in it approves the submission.
29
30 Syntax:
31
32 moderator [:keep]
33 [:address <address: string>]
34 [:program <sieve-program: string>]
35 [:source <sieve-file: string>]
36
37 The moderator action spawns an inferior Sieve machine and filters the
38 original submission (part 2) through it. If the inferior machine marks
39 the message as deleted, the action replies to the control message,
40 thereby causing the submission to be discarded. The From: address of the
41 reply can be modified using :address tag. After discarding the message,
42 moderator marks it as deleted, unless it is given :keep tag.
43
44 If :source tag is given, its argument sieve-file specifies a Sieve
45 source file to be used on the message. If :program tag is given, its
46 argument supplies a Sieve program to be used on this message. Only
47 one of :program or :source may be specified. Supplying them both, or
48 supplying several instances of the same keyword, is an error. The
49 behavior of the action in this case is undefined.
50
51 If neither :program nor :source is supplied, moderator will create
52 a copy of the existing Sieve machine and use it on the message.
53
54 The action checks the message structure: it will bail out if the message
55 does not have exactly 3 MIME parts, or if parts 2 and 3 are not of
56 message/rfc822. It is the responsibility of the caller to make sure
57 the message is actually a valid Mailman moderation request, for example:
58
59 if allof(header :is "Sender" "mailman-bounces@gnu.org",
60 header :is "X-List-Administrivia" "yes")
61 {
62 moderator :source "~/.sieve/mailman.sv";
63 }
64
65 */
66
67 #ifdef HAVE_CONFIG_H
68 # include <config.h>
69 #endif
70
71 #include <unistd.h>
72 #include <mailutils/sieve.h>
73 #include <stdlib.h>
74
75 static int
moderator_filter_message(mu_sieve_machine_t mach,mu_message_t msg,int * pdiscard)76 moderator_filter_message (mu_sieve_machine_t mach,
77 mu_message_t msg, int *pdiscard)
78 {
79 int rc;
80 mu_sieve_machine_t newmach;
81 mu_attribute_t attr;
82 char *arg;
83
84 if (mu_sieve_get_tag (mach, "source", SVT_STRING, &arg))
85 {
86 rc = mu_sieve_machine_clone (mach, &newmach);
87 if (rc)
88 {
89 mu_sieve_error (mach, _("cannot initialize sieve machine: %s"),
90 mu_strerror (rc));
91 return 1;
92 }
93 /* FIXME: This should be configurable:
94 moderator :inherit
95 moderator :debug 2
96 ...
97 */
98
99 rc = mu_sieve_compile (newmach, arg);
100 if (rc)
101 mu_sieve_error (mach, _("cannot compile source `%s'"), arg);
102 }
103 else if (mu_sieve_get_tag (mach, "program", SVT_STRING, &arg))
104 {
105 struct mu_locus_range locrange = MU_LOCUS_RANGE_INITIALIZER;
106
107 rc = mu_sieve_machine_clone (mach, &newmach);
108 if (rc)
109 {
110 mu_sieve_error (mach, _("cannot initialize sieve machine: %s"),
111 mu_strerror (rc));
112 return 1;
113 }
114 mu_sieve_get_locus (mach, &locrange);
115 rc = mu_sieve_compile_text (newmach,
116 arg, strlen (arg),
117 &locrange.beg);
118 if (rc)
119 mu_sieve_error (mach, _("cannot compile subprogram"));
120 }
121 else
122 rc = mu_sieve_machine_dup (mach, &newmach);
123
124 if (rc)
125 return rc;
126
127 mu_message_get_attribute (msg, &attr);
128 mu_attribute_unset_deleted (attr);
129
130 rc = mu_sieve_message (newmach, msg);
131
132 if (rc)
133 mu_sieve_error (newmach, _("failed to run inferior sieve machine"));
134 else
135 *pdiscard = mu_attribute_is_deleted (attr);
136
137 mu_sieve_machine_destroy (&newmach);
138
139 return rc;
140 }
141
142 /* Copy the value of the header field FROM from the email header FROM_HDR to
143 the header field TO in the header TO_HDR, replacing the field, if it
144 exists. */
145 static int
copy_header(mu_sieve_machine_t mach,mu_header_t to_hdr,char * to,mu_header_t from_hdr,char * from)146 copy_header (mu_sieve_machine_t mach,
147 mu_header_t to_hdr, char *to, mu_header_t from_hdr, char *from)
148 {
149 int rc;
150 const char *value = NULL;
151
152 if ((rc = mu_header_sget_value (from_hdr, from, &value)))
153 {
154 mu_sieve_error (mach, _("cannot get `%s:' header: %s"),
155 from, mu_strerror (rc));
156 return rc;
157 }
158 rc = mu_header_set_value (to_hdr, to, value, 1);
159 return rc;
160 }
161
162
163 static int
moderator_discard_message(mu_sieve_machine_t mach,mu_message_t request,const char * from)164 moderator_discard_message (mu_sieve_machine_t mach, mu_message_t request,
165 const char *from)
166 {
167 int rc;
168 mu_message_t reply;
169 mu_header_t repl_hdr, req_hdr;
170 mu_mailer_t mailer;
171
172 rc = mu_message_create (&reply, NULL);
173 if (rc)
174 return rc;
175 rc = mu_message_get_header (reply, &repl_hdr);
176 if (rc)
177 {
178 mu_message_destroy (&reply, NULL);
179 return rc;
180 }
181
182 rc = mu_message_get_header (request, &req_hdr);
183 if (rc)
184 {
185 mu_message_destroy (&reply, NULL);
186 return rc;
187 }
188
189 if (copy_header (mach, repl_hdr, MU_HEADER_TO, req_hdr, MU_HEADER_FROM)
190 || copy_header (mach,
191 repl_hdr, MU_HEADER_SUBJECT, req_hdr, MU_HEADER_SUBJECT))
192 {
193 mu_message_destroy (&reply, NULL);
194 return rc;
195 }
196
197 if (from)
198 mu_header_set_value (repl_hdr, MU_HEADER_FROM, from, 1);
199
200 mailer = mu_sieve_get_mailer (mach);
201 rc = mu_mailer_open (mailer, 0);
202 if (rc)
203 mu_sieve_error (mach, _("cannot open mailer: %s"),
204 mu_strerror (rc));
205 else
206 {
207 rc = mu_mailer_send_message (mailer, reply, NULL, NULL);
208 mu_mailer_close (mailer);
209
210 if (rc)
211 mu_sieve_error (mach, _("cannot send message: %s"),
212 mu_strerror (rc));
213 }
214 mu_message_destroy (&reply, NULL);
215 return rc;
216 }
217
218 int
moderator_message_get_part(mu_sieve_machine_t mach,mu_message_t msg,size_t index,mu_message_t * pmsg)219 moderator_message_get_part (mu_sieve_machine_t mach,
220 mu_message_t msg, size_t index, mu_message_t *pmsg)
221 {
222 int rc;
223 mu_message_t tmp;
224 mu_header_t hdr = NULL;
225 const char *value;
226
227 if ((rc = mu_message_get_part (msg, index, &tmp)))
228 {
229 mu_sieve_error (mach, _("cannot get message part #%lu: %s"),
230 (unsigned long) index, mu_strerror (rc));
231 return 1;
232 }
233
234 mu_message_get_header (tmp, &hdr);
235 if (mu_header_sget_value (hdr, MU_HEADER_CONTENT_TYPE, &value) == 0
236 && memcmp (value, "message/rfc822", 14) == 0)
237 {
238 mu_stream_t str;
239 mu_body_t body;
240
241 mu_message_get_body (tmp, &body);
242 mu_body_get_streamref (body, &str);
243
244 rc = mu_stream_to_message (str, pmsg);
245 mu_stream_destroy (&str);
246 if (rc)
247 {
248 mu_sieve_error (mach,
249 _("cannot convert MIME part stream to message: %s"),
250 mu_strerror (rc));
251 return 1;
252 }
253 }
254 else if (value)
255 {
256 mu_sieve_error (mach,
257 _("expected message type message/rfc822, but found %s"),
258 value);
259 return 1;
260 }
261 else
262 {
263 mu_sieve_error (mach, _("no Content-Type header found"));
264 return 1;
265 }
266 return 0;
267 }
268
269 static int
moderator_action(mu_sieve_machine_t mach)270 moderator_action (mu_sieve_machine_t mach)
271 {
272 mu_message_t msg, orig;
273 int rc;
274 size_t nparts = 0;
275 int discard = 0;
276 int ismime;
277
278 msg = mu_sieve_get_message (mach);
279 mu_message_is_multipart (msg, &ismime);
280
281 if (!ismime)
282 {
283 mu_sieve_error (mach, _("message is not multipart"));
284 mu_sieve_abort (mach);
285 }
286
287 rc = mu_message_get_num_parts (msg, &nparts);
288 if (rc)
289 {
290 mu_sieve_error (mach, "mu_message_get_num_parts: %s", mu_strerror (rc));
291 mu_sieve_abort (mach);
292 }
293
294 if (nparts != 3) /* Mailman moderation requests have three parts */
295 {
296 mu_sieve_error (mach, _("expected 3 parts, but found %lu"),
297 (unsigned long) nparts);
298 mu_sieve_abort (mach);
299 }
300
301 if ((rc = moderator_message_get_part (mach, msg, 2, &orig)))
302 mu_sieve_abort (mach);
303
304 rc = moderator_filter_message (mach, orig, &discard);
305 mu_message_unref (orig);
306 if (rc)
307 mu_sieve_abort (mach);
308
309 if (discard && !mu_sieve_is_dry_run (mach))
310 {
311 mu_message_t request;
312 char *from = NULL;
313
314 if ((rc = moderator_message_get_part (mach, msg, 3, &request)))
315 {
316 mu_sieve_error (mach, _("cannot get message part #3: %s"),
317 mu_strerror (rc));
318 mu_sieve_abort (mach);
319 }
320
321 mu_sieve_get_tag (mach, "address", SVT_STRING, &from);
322
323 if (moderator_discard_message (mach, request, from))
324 discard = 0;
325 else
326 {
327 if (!mu_sieve_get_tag (mach, "keep", SVT_VOID, NULL))
328 {
329 mu_attribute_t attr = 0;
330
331 if (mu_message_get_attribute (msg, &attr) == 0)
332 mu_attribute_set_deleted (attr);
333 }
334 else
335 discard = 0;
336 }
337 mu_message_unref (request);
338 }
339
340 mu_sieve_log_action (mach, "MODERATOR",
341 discard ? _("discarding message") :
342 _("keeping message"));
343 return 0;
344 }
345
346
347 /* Initialization */
348
349 /* Required arguments: */
350 static mu_sieve_data_type moderator_req_args[] = {
351 SVT_VOID
352 };
353
354 /* Tagged arguments: */
355 static mu_sieve_tag_def_t moderator_tags[] = {
356 { "keep", SVT_VOID },
357 { "address", SVT_STRING },
358 { "source", SVT_STRING },
359 { "program", SVT_STRING },
360 { NULL }
361 };
362
363 static mu_sieve_tag_group_t moderator_tag_groups[] = {
364 { moderator_tags, NULL },
365 { NULL }
366 };
367
368
369 /* Initialization function. */
370 int
SIEVE_EXPORT(moderator,init)371 SIEVE_EXPORT(moderator,init) (mu_sieve_machine_t mach)
372 {
373 mu_sieve_register_action (mach, "moderator", moderator_action,
374 moderator_req_args,
375 moderator_tag_groups, 1);
376 return 0;
377 }
378
379