1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 #include "common.h"
54 #include "str.h"
55 #include "strfuncs.h"
56 #include "commands.h"
57 #include "imap-search.h"
58 #include "lib-storage/mail-storage.h"
59 #include "lib/mempool.h"
60 #include "mail-storage.h"
61 #include <unistd.h>
62 #include <sys/wait.h>
63 #include <stdlib.h>
64 #include <stdio.h>
65 #include <errno.h>
66 #include <fcntl.h>
67 #ifdef DEBUG
68 #include <syslog.h>
69 #endif
70
71 #define SIGHEADERLINE "X-DSPAM-Signature"
72 #define MAXSIGLEN 100
73
74 #ifndef DSPAM
75 #define DSPAM "/usr/bin/dspam"
76 #endif /* DSPAM */
77
78 static int
79 call_dspam(const char* signature, int is_spam)
80 {
81 pid_t pid;
82 int s;
83 char class_arg[16+2];
84 char sign_arg[MAXSIGLEN+2];
85 int pipes[2];
86
87 s = snprintf(sign_arg, 101, "--signature=%s", signature);
88 if ( s > MAXSIGLEN || s <= 0) return -1;
89
90 snprintf(class_arg, 17, "--class=%s", is_spam ? "spam" : "innocent");
91
92 pipe(pipes);
93
94 pid = fork();
95 if (pid < 0) return -1;
96
97 if (pid) {
98 int status;
99
100
101
102
103
104 char buf[1024];
105 int readsize;
106 close(pipes[1]);
107
108 do {
109 readsize = read(pipes[0], buf, 1024);
110 if (readsize < 0) {
111 readsize = -1;
112 if (errno == EINTR) readsize = -2;
113 }
114 } while (readsize == -2);
115
116 if (readsize != 0) {
117 close(pipes[0]);
118 return -1;
119 }
120
121 waitpid (pid, &status, 0);
122 if (!WIFEXITED(status)) {
123 close(pipes[0]);
124 return -1;
125 }
126
127 readsize = read(pipes[0], buf, 1024);
128 if (readsize != 0) {
129 close(pipes[0]);
130 return -1;
131 }
132
133 close(pipes[0]);
134 return WEXITSTATUS(status);
135 } else {
136 int fd = open("/dev/null", O_RDONLY);
137 close(0); close(1); close(2);
138
139 close(pipes[0]);
140
141 if (dup2(pipes[1], 2) != 2) {
142 exit(1);
143 }
144 if (dup2(pipes[1], 1) != 1) {
145 exit(1);
146 }
147 close(pipes[1]);
148
149 if (dup2(fd, 0) != 0) {
150 exit(1);
151 }
152 close(fd);
153
154 #ifdef DEBUG
155 syslog(LOG_INFO, DSPAM " --source=error --stdout %s %s", class_arg, sign_arg);
156 #endif
157 execl (DSPAM, DSPAM, "--source=error", "--stdout", class_arg, sign_arg, NULL);
158 exit(127);
159 return -1;
160 }
161 }
162
163 struct dspam_signature_list {
164 struct dspam_signature_list * next;
165 char * sig;
166 };
167 typedef struct dspam_signature_list * siglist_t;
168
169 static siglist_t list_append(pool_t pool, siglist_t * list) {
170 siglist_t l = *list;
171 siglist_t p = NULL;
172 siglist_t n;
173
174 while (l != NULL) {
175 p = l;
176 l = l->next;
177 }
178 n = p_malloc (pool, sizeof(struct dspam_signature_list));
179 n->next = NULL;
180 n->sig = NULL;
181 if (p == NULL) {
182 *list = n;
183 } else {
184 p->next = n;
185 }
186 return n;
187 }
188
189 static int
190 fetch_and_copy_reclassified (struct mailbox_transaction_context *t,
191 struct mailbox *srcbox,
192 struct mail_search_arg *search_args,
193 int is_spam,
194 int *enh_error)
195 {
196 struct mail_search_context *search_ctx;
197 struct mailbox_transaction_context *src_trans;
198 struct mail_keywords *keywords;
199 const char *const *keywords_list;
200 struct mail *mail;
201 int ret;
202
203 const char* signature;
204 struct dspam_signature_list * siglist = NULL;
205 pool_t listpool = pool_alloconly_create("dspam-siglist-pool", 1024);
206
207 *enh_error = 0;
208
209 src_trans = mailbox_transaction_begin(srcbox, 0);
210 search_ctx = mailbox_search_init(src_trans, NULL, search_args, NULL);
211
212 mail = mail_alloc(src_trans, MAIL_FETCH_STREAM_HEADER |
213 MAIL_FETCH_STREAM_BODY, NULL);
214 ret = 1;
215 while (mailbox_search_next(search_ctx, mail) > 0 && ret > 0) {
216 if (mail->expunged) {
217 ret = 0;
218 break;
219 }
220
221 signature = mail_get_first_header(mail, SIGHEADERLINE);
222 if (is_empty_str(signature)) {
223 ret = -1;
224 *enh_error = -2;
225 break;
226 }
227 list_append(listpool, &siglist)->sig = p_strdup(listpool, signature);
228
229 keywords_list = mail_get_keywords(mail);
230 keywords = strarray_length(keywords_list) == 0 ? NULL :
231 mailbox_keywords_create(t, keywords_list);
232 if (mailbox_copy(t, mail, mail_get_flags(mail),
233 keywords, NULL) < 0)
234 ret = -1;
235 mailbox_keywords_free(t, &keywords);
236 }
237 mail_free(&mail);
238
239 if (mailbox_search_deinit(&search_ctx) < 0)
240 ret = -1;
241
242
243 while (siglist) {
244 if ((*enh_error = call_dspam (siglist->sig, is_spam))) {
245 ret = -1;
246 break;
247 }
248 siglist = siglist->next;
249 }
250
251 pool_unref (listpool);
252
253 if (*enh_error) {
254 mailbox_transaction_rollback(&src_trans);
255 } else {
256 if (mailbox_transaction_commit(&src_trans, 0) < 0)
257 ret = -1;
258 }
259
260 return ret;
261 }
262
263 static bool cmd_append_spam_plugin(struct client_command_context *cmd)
264 {
265 const char *mailbox;
266 struct mail_storage *storage;
267 struct mailbox *box;
268
269
270 if (!client_read_string_args(cmd, 1, &mailbox))
271 return FALSE;
272
273 storage = client_find_storage(cmd, &mailbox);
274 if (storage == NULL)
275 return FALSE;
276
277 box = mailbox_open(storage, mailbox, NULL, MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT);
278 if (box != NULL)
279 {
280
281
282 if (mailbox_equals(box, storage, "SPAM"))
283 {
284 mailbox_close(&box);
285 return cmd_sync (cmd, 0, 0, "NO Cannot APPEND to SPAM box, sorry.");
286 }
287
288 mailbox_close(&box);
289 }
290
291 return cmd_append (cmd);
292 }
293
294 static bool cmd_copy_spam_plugin(struct client_command_context *cmd)
295 {
296 struct client *client = cmd->client;
297 struct mail_storage *storage;
298 struct mailbox *destbox;
299 struct mailbox_transaction_context *t;
300 struct mail_search_arg *search_arg;
301 const char *messageset, *mailbox;
302 enum mailbox_sync_flags sync_flags = 0;
303 int ret;
304 int spam_folder = 0;
305 int enh_error = 0, is_spam;
306 #ifdef IGNORE_TRASH_NAME
307 int is_trash;
308 int trash_folder = 0;
309 #endif
310 struct mailbox *box;
311
312
313 if (!client_read_string_args(cmd, 2, &messageset, &mailbox))
314 return FALSE;
315
316 if (!client_verify_open_mailbox(cmd))
317 return TRUE;
318
319 storage = client_find_storage(cmd, &mailbox);
320 if (storage == NULL)
321 return FALSE;
322 box = mailbox_open(storage, mailbox, NULL, MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT);
323 if (!box) {
324 client_send_storage_error(cmd, storage);
325 return TRUE;
326 }
327
328 is_spam = mailbox_equals(box, storage, "SPAM");
329 spam_folder = is_spam || mailbox_equals(cmd->client->mailbox, storage, "SPAM");
330 #ifdef IGNORE_TRASH_NAME
331 is_trash = mailbox_equals(box, storage, IGNORE_TRASH_NAME);
332 trash_folder = is_trash || mailbox_equals(cmd->client->mailbox, storage, IGNORE_TRASH_NAME);
333 #endif
334
335 mailbox_close(&box);
336
337
338 if (!spam_folder)
339 return cmd_copy(cmd);
340 #ifdef IGNORE_TRASH_NAME
341
342
343
344
345
346 if (trash_folder)
347 return cmd_copy(cmd);
348 #endif
349
350
351
352 if (!client_verify_mailbox_name(cmd, mailbox, TRUE, FALSE))
353 return TRUE;
354
355 search_arg = imap_search_get_arg(cmd, messageset, cmd->uid);
356 if (search_arg == NULL)
357 return TRUE;
358
359 storage = client_find_storage(cmd, &mailbox);
360 if (storage == NULL)
361 return TRUE;
362
363 if (mailbox_equals(client->mailbox, storage, mailbox))
364 destbox = client->mailbox;
365 else {
366 destbox = mailbox_open(storage, mailbox, NULL,
367 MAILBOX_OPEN_FAST |
368 MAILBOX_OPEN_KEEP_RECENT);
369 if (destbox == NULL) {
370 client_send_storage_error(cmd, storage);
371 return TRUE;
372 }
373 }
374
375 t = mailbox_transaction_begin(destbox,
376 MAILBOX_TRANSACTION_FLAG_EXTERNAL);
377 ret = fetch_and_copy_reclassified(t, client->mailbox, search_arg, is_spam, &enh_error);
378
379 if (ret <= 0)
380 mailbox_transaction_rollback(&t);
381 else {
382 if (mailbox_transaction_commit(&t, 0) < 0)
383 ret = -1;
384 }
385
386 if (destbox != client->mailbox) {
387 sync_flags |= MAILBOX_SYNC_FLAG_FAST;
388 mailbox_close(&destbox);
389 }
390
391 if (ret > 0)
392 return cmd_sync(cmd, sync_flags, 0, "OK Copy completed.");
393 else if (ret == 0) {
394
395 return cmd_sync(cmd, 0, 0,
396 "NO Some of the requested messages no longer exist.");
397 } else {
398 switch (enh_error) {
399 case -2:
400 return cmd_sync(cmd, 0, 0, "NO Some messages did not have " SIGHEADERLINE " header line");
401 break;
402 case -3:
403 return cmd_sync(cmd, 0, 0, "NO Failed to call dspam");
404 break;
405 case 0:
406 client_send_storage_error(cmd, storage);
407 return TRUE;
408 break;
409 default:
410 return cmd_sync(cmd, 0, 0, "NO dspam failed");
411 break;
412 }
413 }
414
415 return TRUE;
416 }
417
418 void dspam_init(void)
419 {
420 command_unregister("COPY");
421 command_unregister("APPEND");
422 command_unregister("UID COPY");
423
424
425
426 command_register(i_strdup("COPY"), cmd_copy_spam_plugin);
427 command_register(i_strdup("UID COPY"), cmd_copy_spam_plugin);
428 command_register(i_strdup("APPEND"), cmd_append_spam_plugin);
429 }
430
431 void dspam_deinit(void)
432 {
433 }