Automatic moderator for Mailman-driven mailing lists.
Showing
1 changed file
with
353 additions
and
0 deletions
libsieve/extensions/moderator.c
0 → 100644
1 | /* GNU Mailutils -- a suite of utilities for electronic mail | ||
2 | Copyright (C) 2006 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 2, 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, write to the Free | ||
16 | Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
17 | Boston, MA 02110-1301 USA */ | ||
18 | |||
19 | /* Moderator robot for Mailman-driven mail archives. | ||
20 | Mailman moderation request is a MIME message consisting of the three parts: | ||
21 | |||
22 | 1 text/plain Introduction for the human reader. | ||
23 | 2 message/rfc822 Original submission. | ||
24 | 3 message/rfc822 Mailman control message. | ||
25 | |||
26 | Replying to part 3 (keeping the subject intact) instructs Mailman to | ||
27 | discard the original submission. | ||
28 | Replying to part 3 while adding an `Approved:' header with the list | ||
29 | password in it approves the submission. | ||
30 | |||
31 | Syntax: | ||
32 | |||
33 | moderator [:keep] | ||
34 | [:address <address: 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 the Sieve | ||
45 | source file to be used on the message. Otherwise, moderator will create | ||
46 | a copy of the existing Sieve machine. | ||
47 | |||
48 | The action checks the message structure: it will bail out if the message | ||
49 | does not have exactly 3 MIME parts, or if parts 2 and 3 are not of | ||
50 | message/rfc822. It is the responsibility of the caller to make sure | ||
51 | the message is actually a valid Mailman moderation request, for example: | ||
52 | |||
53 | if allof(header :is "Sender" "mailman-bounces@gnu.org", | ||
54 | header :is "X-List-Administrivia" "yes") | ||
55 | { | ||
56 | moderator "~/.sieve/mailman.sv"; | ||
57 | } | ||
58 | |||
59 | */ | ||
60 | |||
61 | #ifdef HAVE_CONFIG_H | ||
62 | # include <config.h> | ||
63 | #endif | ||
64 | |||
65 | #include <unistd.h> | ||
66 | #include <mailutils/libsieve.h> | ||
67 | #include <stdlib.h> | ||
68 | |||
69 | static int | ||
70 | moderator_filter_message (mu_sieve_machine_t mach, mu_list_t tags, | ||
71 | mu_message_t msg, int *pdiscard) | ||
72 | { | ||
73 | int rc; | ||
74 | mu_sieve_machine_t newmach; | ||
75 | mu_attribute_t attr; | ||
76 | mu_sieve_value_t *arg; | ||
77 | |||
78 | if (mu_sieve_tag_lookup (tags, "source", &arg)) | ||
79 | { | ||
80 | rc = mu_sieve_machine_init (&newmach, NULL); | ||
81 | if (rc) | ||
82 | { | ||
83 | mu_sieve_error (mach, _("Cannot initialize sieve machine: %s"), | ||
84 | mu_strerror (rc)); | ||
85 | return 1; | ||
86 | } | ||
87 | /* FIXME: This should be configurable: | ||
88 | moderator :inherit | ||
89 | moderator :debug 2 | ||
90 | ... | ||
91 | */ | ||
92 | mu_sieve_machine_inherit_report (newmach, mach); | ||
93 | |||
94 | rc = mu_sieve_compile (newmach, arg->v.string); | ||
95 | if (rc) | ||
96 | mu_sieve_error (mach, _("cannot compile source `%s'"), arg->v.string); | ||
97 | } | ||
98 | else | ||
99 | rc = mu_sieve_machine_dup (mach, &newmach); | ||
100 | |||
101 | if (rc) | ||
102 | return rc; | ||
103 | |||
104 | mu_message_get_attribute (msg, &attr); | ||
105 | mu_attribute_unset_deleted (attr); | ||
106 | |||
107 | rc = mu_sieve_message (newmach, msg); | ||
108 | |||
109 | if (rc) | ||
110 | mu_sieve_error (newmach, _("failed to run inferior sieve machine")); | ||
111 | else | ||
112 | *pdiscard = mu_attribute_is_deleted (attr); | ||
113 | |||
114 | mu_sieve_machine_destroy (&newmach); | ||
115 | |||
116 | return rc; | ||
117 | } | ||
118 | |||
119 | static int | ||
120 | copy_header (mu_sieve_machine_t mach, | ||
121 | mu_header_t to_hdr, char *to, mu_header_t from_hdr, char *from) | ||
122 | { | ||
123 | int rc; | ||
124 | char *value = NULL; | ||
125 | if ((rc = mu_header_aget_value (from_hdr, from, &value))) | ||
126 | { | ||
127 | mu_sieve_error (mach, _("cannot get `%s:' header: %s"), | ||
128 | from, mu_strerror (rc)); | ||
129 | return rc; | ||
130 | } | ||
131 | rc = mu_header_set_value (to_hdr, to, value, 0); | ||
132 | free (value); | ||
133 | return rc; | ||
134 | } | ||
135 | |||
136 | |||
137 | static int | ||
138 | moderator_discard_message (mu_sieve_machine_t mach, mu_message_t request, | ||
139 | const char *from) | ||
140 | { | ||
141 | int rc; | ||
142 | mu_message_t reply; | ||
143 | mu_header_t repl_hdr, req_hdr; | ||
144 | mu_mailer_t mailer; | ||
145 | |||
146 | rc = mu_message_create (&reply, NULL); | ||
147 | if (rc) | ||
148 | return rc; | ||
149 | rc = mu_message_get_header (reply, &repl_hdr); | ||
150 | if (rc) | ||
151 | { | ||
152 | mu_message_destroy (&reply, NULL); | ||
153 | return rc; | ||
154 | } | ||
155 | |||
156 | rc = mu_message_get_header (request, &req_hdr); | ||
157 | if (rc) | ||
158 | { | ||
159 | mu_message_destroy (&reply, NULL); | ||
160 | return rc; | ||
161 | } | ||
162 | |||
163 | if (copy_header (mach, repl_hdr, MU_HEADER_TO, req_hdr, MU_HEADER_FROM) | ||
164 | || copy_header (mach, | ||
165 | repl_hdr, MU_HEADER_SUBJECT, req_hdr, MU_HEADER_SUBJECT)) | ||
166 | { | ||
167 | mu_message_destroy (&reply, NULL); | ||
168 | return rc; | ||
169 | } | ||
170 | |||
171 | if (from) | ||
172 | mu_header_set_value (repl_hdr, MU_HEADER_FROM, from, 0); | ||
173 | |||
174 | mailer = mu_sieve_get_mailer (mach); | ||
175 | rc = mu_mailer_open (mailer, 0); | ||
176 | if (rc) | ||
177 | mu_sieve_error (mach, _("cannot open mailer: %s"), | ||
178 | mu_strerror (rc)); | ||
179 | else | ||
180 | { | ||
181 | rc = mu_mailer_send_message (mailer, reply, NULL, NULL); | ||
182 | mu_mailer_close (mailer); | ||
183 | |||
184 | if (rc) | ||
185 | mu_sieve_error (mach, _("cannot send message: %s"), | ||
186 | mu_strerror (rc)); | ||
187 | } | ||
188 | mu_message_destroy (&reply, NULL); | ||
189 | return rc; | ||
190 | } | ||
191 | |||
192 | int | ||
193 | moderator_message_get_part (mu_sieve_machine_t mach, | ||
194 | mu_message_t msg, size_t index, mu_message_t *pmsg) | ||
195 | { | ||
196 | int rc; | ||
197 | mu_message_t tmp; | ||
198 | mu_header_t hdr = NULL; | ||
199 | char *value; | ||
200 | |||
201 | if ((rc = mu_message_get_part (msg, index, &tmp))) | ||
202 | { | ||
203 | mu_sieve_error (mach, _("cannot get message part #%lu: %s"), | ||
204 | (unsigned long) index, mu_strerror (rc)); | ||
205 | return 1; | ||
206 | } | ||
207 | |||
208 | mu_message_get_header (tmp, &hdr); | ||
209 | if (mu_header_aget_value (hdr, MU_HEADER_CONTENT_TYPE, &value) == 0 | ||
210 | && memcmp (value, "message/rfc822", 14) == 0) | ||
211 | { | ||
212 | mu_stream_t str; | ||
213 | mu_body_t body; | ||
214 | |||
215 | free (value); | ||
216 | mu_message_get_body (tmp, &body); | ||
217 | mu_body_get_stream (body, &str); | ||
218 | |||
219 | rc = mu_stream_to_message (str, pmsg); | ||
220 | if (rc) | ||
221 | { | ||
222 | mu_sieve_error (mach, | ||
223 | _("cannot convert MIME part stream to message: %s"), | ||
224 | mu_strerror (rc)); | ||
225 | return 1; | ||
226 | } | ||
227 | } | ||
228 | else if (value) | ||
229 | { | ||
230 | mu_sieve_error (mach, | ||
231 | _("expected message type message/rfc822, but found %s"), | ||
232 | value); | ||
233 | free (value); | ||
234 | return 1; | ||
235 | } | ||
236 | else | ||
237 | { | ||
238 | mu_sieve_error (mach, _("No Content-Type header found")); | ||
239 | return 1; | ||
240 | } | ||
241 | return 0; | ||
242 | } | ||
243 | |||
244 | static int | ||
245 | moderator_action (mu_sieve_machine_t mach, mu_list_t args, mu_list_t tags) | ||
246 | { | ||
247 | mu_message_t msg, orig; | ||
248 | int rc; | ||
249 | size_t nparts = 0; | ||
250 | int discard = 0; | ||
251 | int ismime; | ||
252 | |||
253 | if (mu_sieve_get_debug_level (mach) & MU_SIEVE_DEBUG_TRACE) | ||
254 | { | ||
255 | mu_sieve_locus_t locus; | ||
256 | mu_sieve_get_locus (mach, &locus); | ||
257 | mu_sieve_debug (mach, "%s:%lu: moderator_test %lu\n", | ||
258 | locus.source_file, | ||
259 | (u_long) locus.source_line, | ||
260 | (u_long) mu_sieve_get_message_num (mach)); | ||
261 | } | ||
262 | |||
263 | msg = mu_sieve_get_message (mach); | ||
264 | mu_message_is_multipart (msg, &ismime); | ||
265 | |||
266 | if (!ismime) | ||
267 | { | ||
268 | mu_sieve_error (mach, _("message is not multipart")); | ||
269 | mu_sieve_abort (mach); | ||
270 | } | ||
271 | |||
272 | mu_message_get_num_parts (msg, &nparts); | ||
273 | |||
274 | if (nparts != 3) /* Mailman moderation requests have three parts */ | ||
275 | { | ||
276 | mu_sieve_error (mach, _("expected 3 parts, but found %lu"), | ||
277 | (unsigned long) nparts); | ||
278 | mu_sieve_abort (mach); | ||
279 | } | ||
280 | |||
281 | if ((rc = moderator_message_get_part (mach, msg, 2, &orig))) | ||
282 | mu_sieve_abort (mach); | ||
283 | |||
284 | rc = moderator_filter_message (mach, tags, orig, &discard); | ||
285 | mu_message_unref (orig); | ||
286 | if (rc) | ||
287 | mu_sieve_abort (mach); | ||
288 | |||
289 | if (discard && !mu_sieve_is_dry_run (mach)) | ||
290 | { | ||
291 | mu_message_t request; | ||
292 | char *from = NULL; | ||
293 | mu_sieve_value_t *arg; | ||
294 | |||
295 | if ((rc = moderator_message_get_part (mach, msg, 3, &request))) | ||
296 | { | ||
297 | mu_sieve_error (mach, _("cannot get message part #3: %s"), | ||
298 | mu_strerror (rc)); | ||
299 | mu_sieve_abort (mach); | ||
300 | } | ||
301 | |||
302 | if (mu_sieve_tag_lookup (tags, "address", &arg)) | ||
303 | from = arg->v.string; | ||
304 | |||
305 | if (moderator_discard_message (mach, request, from)) | ||
306 | discard = 0; | ||
307 | else | ||
308 | { | ||
309 | if (!mu_sieve_tag_lookup (tags, "keep", NULL)) | ||
310 | sieve_mark_deleted (msg, 1); | ||
311 | else | ||
312 | discard = 0; | ||
313 | } | ||
314 | mu_message_unref (request); | ||
315 | } | ||
316 | |||
317 | mu_sieve_log_action (mach, "MODERATOR", | ||
318 | discard ? _("discarding message") : | ||
319 | _("keeping message")); | ||
320 | return 0; | ||
321 | } | ||
322 | |||
323 | |||
324 | /* Initialization */ | ||
325 | |||
326 | /* Required arguments: */ | ||
327 | static mu_sieve_data_type moderator_req_args[] = { | ||
328 | SVT_VOID | ||
329 | }; | ||
330 | |||
331 | /* Tagged arguments: */ | ||
332 | static mu_sieve_tag_def_t moderator_tags[] = { | ||
333 | { "keep", SVT_VOID }, | ||
334 | { "address", SVT_STRING }, | ||
335 | { "source", SVT_STRING }, | ||
336 | { NULL } | ||
337 | }; | ||
338 | |||
339 | static mu_sieve_tag_group_t moderator_tag_groups[] = { | ||
340 | { moderator_tags, NULL }, | ||
341 | { NULL } | ||
342 | }; | ||
343 | |||
344 | |||
345 | /* Initialization function. */ | ||
346 | int | ||
347 | SIEVE_EXPORT(moderator,init) (mu_sieve_machine_t mach) | ||
348 | { | ||
349 | return mu_sieve_register_action (mach, "moderator", moderator_action, | ||
350 | moderator_req_args, | ||
351 | moderator_tag_groups, 1); | ||
352 | } | ||
353 |
-
Please register or sign in to post a comment