/[imapfilter]/imapfilter/action.c
ViewVC logotype

Contents of /imapfilter/action.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.17 - (show annotations)
Sun Feb 15 15:32:38 2004 UTC (20 years, 1 month ago) by lefcha
Branch: MAIN
CVS Tags: HEAD
Changes since 1.16: +122 -9 lines
File MIME type: text/plain
Added message headers' variables for From: header.

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <time.h>
6
7 #include "config.h"
8 #include "imapfilter.h"
9 #include "filter.h"
10
11
12 extern connection_t connpri, connaux;
13 extern options_t opts;
14
15
16 int action_delete(char *mesgs, char *args);
17 int action_copy(char *mbox, char *mesgs, char *destmbox, char *args);
18 int action_move(char *mbox, char *mesgs, char *destmbox, char *args);
19 int action_rcopy(char *mbox, char *mesgs, account_t * destacct, char *destmbox, char *args);
20 int action_rmove(char *mbox, char *mesgs, account_t * destacct, char *destmbox, char *args);
21 int action_flag(char *mesgs, unsigned int *type, unsigned int *msgflags, char *args);
22 int action_list(char *mesgs, char *args);
23
24 unsigned int count_messages(char *mesgs);
25 char *convert_messages(char *mesgs);
26 int substitute_date(char *str);
27 void system_date(char *destmbox);
28 void message_date(char *mesg, char *destmbox);
29 void default_variables(char *mbox, char *destmbox);
30 void headers_variables(char *mesg, char *destmbox);
31
32
33 /*
34 * Apply the appropriate action.
35 */
36 int
37 apply_action(char *mbox, char *mesgs, unsigned int *type, account_t * destacct,
38 char *destmbox, unsigned int *msgflags, char *args)
39 {
40 unsigned int cnt;
41
42 if (*mesgs == '\0')
43 return 0;
44
45 log_info(LOG_ACTION, type);
46 log_info(LOG_DEST_ACCOUNT, destacct->key);
47 log_info(LOG_DEST_MBOX, destmbox);
48
49 cnt = count_messages(mesgs);
50
51 switch (*type) {
52 case ACTION_DELETE:
53 info("%d message%s deleted.\n", cnt, plural(cnt));
54 action_delete(mesgs, args);
55 break;
56 case ACTION_COPY:
57 info("%d message%s copied from \"%s\" to mailbox \"%s\".\n",
58 cnt, plural(cnt), mbox, destmbox);
59 action_copy(mbox, mesgs, apply_namespace(destmbox,
60 connpri.ns.prefix, connpri.ns.delim), args);
61 break;
62 case ACTION_MOVE:
63 info("%d message%s moved from \"%s\" to mailbox \"%s\".\n",
64 cnt, plural(cnt), mbox, destmbox);
65 action_move(mbox, mesgs, apply_namespace(destmbox,
66 connpri.ns.prefix, connpri.ns.delim), args);
67 break;
68 case ACTION_RCOPY:
69 info("%d message%s copied from \"%s\" to mailbox "
70 "\"%s\" at account %s.\n", cnt, plural(cnt), mbox,
71 destmbox, destacct->key);
72 action_rcopy(mbox, mesgs, destacct, destmbox, args);
73 break;
74 case ACTION_RMOVE:
75 info("%d message%s moved from \"%s\" to mailbox "
76 "\"%s\" at account %s.\n", cnt, plural(cnt), mbox,
77 destmbox, destacct->key);
78 action_rmove(mbox, mesgs, destacct, destmbox, args);
79 break;
80 case ACTION_FLAG_REPLACE:
81 case ACTION_FLAG_ADD:
82 case ACTION_FLAG_REMOVE:
83 info("%d message%s flagged.\n", cnt, plural(cnt));
84 action_flag(mesgs, type, msgflags, args);
85 break;
86 case ACTION_LIST:
87 info("%d message%s listed.\n", cnt, plural(cnt));
88 action_list(mesgs, args);
89 break;
90 }
91
92 if (*args == '\0')
93 log_info(LOG_PREAMBLE, NULL);
94
95 return 0;
96 }
97
98
99 /*
100 * Delete messages and optionally list some of their headers.
101 */
102 int
103 action_delete(char *mesgs, char *args)
104 {
105 char *tok, *m, *mcp;
106
107 action_list(mesgs, args);
108
109 m = mcp = convert_messages(mesgs);
110
111 tok = strtok_r(m, " ", &m);
112 while (tok) {
113 response_generic(&connpri, imap_store(&connpri, tok,
114 ACTION_FLAG_ADD, "\\Deleted"));
115
116 tok = strtok_r(NULL, " ", &m);
117 }
118
119 if (opts.expunge)
120 response_generic(&connpri, imap_expunge(&connpri));
121
122 xfree(mcp);
123
124 return 0;
125 }
126
127
128 /*
129 * Copy messages to specified mailbox.
130 */
131 int
132 action_copy(char *mbox, char *mesgs, char *destmbox, char *args)
133 {
134 int r;
135 char *tok, *mcp, *m;
136 char dm[2][MBOX_LEN];
137
138 r = 0;
139 tok = NULL;
140 *dm[0] = *dm[1] = '\0';
141
142 action_list(mesgs, args);
143
144 if (strchr(destmbox, '@') || strchr(destmbox, '&'))
145 m = mcp = xstrdup(mesgs);
146 else
147 m = mcp = convert_messages(mesgs);
148
149 xstrncpy(dm[0], destmbox, MBOX_LEN - 1);
150 default_variables(mbox, dm[0]);
151 system_date(dm[0]);
152 tok = strtok_r(m, " ", &m);
153 while (tok != NULL) {
154 xstrncpy(dm[1], dm[0], MBOX_LEN - 1);
155 message_date(tok, dm[1]);
156 headers_variables(tok, dm[1]);
157
158 if ((r = response_copy(&connpri, imap_copy(&connpri, tok,
159 dm[1]))) == RESPONSE_TRYCREATE)
160 if (!response_generic(&connpri, imap_create(&connpri,
161 dm[1]))) {
162 if (opts.subscribe)
163 response_generic(&connpri,
164 imap_subscribe(&connpri, dm[1]));
165 r = response_copy(&connpri,
166 imap_copy(&connpri, tok, dm[1]));
167 }
168 tok = strtok_r(NULL, " ", &m);
169 }
170
171 xfree(mcp);
172
173 return r;
174 }
175
176
177 /*
178 * Move messages to specified mailbox.
179 */
180 int
181 action_move(char *mbox, char *mesgs, char *destmbox, char *args)
182 {
183
184 if (!action_copy(mbox, mesgs, destmbox, args))
185 action_delete(mesgs, "\0");
186
187 return 0;
188 }
189
190
191 /*
192 * Copy messages to the specified mailbox of another mail server.
193 */
194 int
195 action_rcopy(char *mbox, char *mesgs, account_t * destacct, char *destmbox,
196 char *args)
197 {
198 int r, ta, tf;
199 char *tok, *m, *mcp, *ndm;
200 char *flags, *date;
201 unsigned int size;
202 char buf[RESPONSE_BUF * 2 + 1];
203 char dm[3][MBOX_LEN];
204
205 *dm[0] = *dm[1] = *dm[2] = '\0';
206 flags = date = NULL;
207
208 if (init_connection(&connaux, destacct->server, destacct->port,
209 destacct->ssl))
210 return ERROR_NETWORK;
211
212 r = response_greeting(&connaux);
213
214 if (opts.debug)
215 test(&connaux);
216
217 if (r == RESPONSE_BYE || check_capabilities(&connaux))
218 return ERROR_NETWORK;
219
220 #ifdef SSL_TLS
221 if (destacct->ssl == SSL_DISABLED && connaux.caps & CAPABILITY_STARTTLS)
222 if (negotiate_tls(&connaux) == RESPONSE_OK)
223 check_capabilities(&connaux);
224 #endif
225
226 if (r != RESPONSE_PREAUTH) {
227 if (destacct->pass_attr == PASS_ATTR_NONE) {
228 printf("Enter password for %s@%s: ", destacct->user,
229 destacct->server);
230 get_password(destacct->pass, PASS_LEN);
231 destacct->pass_attr = PASS_ATTR_PLAIN;
232 }
233 #ifdef CRAM_MD5
234 if (connaux.caps & CAPABILITY_CRAMMD5)
235 r = auth_cram_md5(&connaux, destacct->user,
236 destacct->pass);
237 else
238 #endif
239 r = login(&connaux, destacct->user, destacct->pass);
240
241 if (r == RESPONSE_NO) {
242 error("username %s or password rejected at %s\n",
243 destacct->user, destacct->server);
244 return ERROR_NETWORK;
245 }
246 }
247 check_namespace(&connaux);
248
249 m = mcp = xstrdup(mesgs);
250
251 xstrncpy(dm[1], destmbox, MBOX_LEN - 1);
252 default_variables(mbox, dm[1]);
253 system_date(dm[1]);
254
255 tok = strtok_r(m, " ", &m);
256 while (tok != NULL) {
257 xstrncpy(dm[2], dm[1], MBOX_LEN - 1);
258 message_date(tok, dm[2]);
259 headers_variables(tok, dm[2]);
260
261 /* apply_namespace() returns a pointer to a static buffer. */
262 ndm = apply_namespace(dm[2], connaux.ns.prefix,
263 connaux.ns.delim);
264
265 /* Check only if mailbox name is different from last one. */
266 if (strncmp(dm[0], dm[2], strlen(dm[2]))) {
267 r = check_mailbox(&connaux, ndm);
268 if (r == RESPONSE_NO) {
269 response_generic(&connaux,
270 imap_create(&connaux, ndm));
271 if (opts.subscribe)
272 response_generic(&connaux,
273 imap_subscribe(&connaux, ndm));
274 }
275 }
276 xstrncpy(dm[0], dm[2], MBOX_LEN - 1);
277
278 response_fetchfast(&connpri, &flags, &date, &size,
279 imap_fetch(&connpri, tok, "FAST"));
280
281 ta = imap_append(&connaux, ndm, flags, date, size);
282
283 xfree(flags);
284 xfree(date);
285
286 response_fetch(&connpri, 0, 1, NULL);
287 tf = imap_fetch(&connpri, tok, opts.peek ?
288 "BODY.PEEK[HEADER]" : "BODY[HEADER]");
289 do {
290 r = response_fetch(&connpri, tf, 0, buf);
291 socket_write(&connaux, buf);
292 } while (r == RESPONSE_NONE);
293
294 socket_write(&connaux, "\r\n");
295
296 response_fetch(&connpri, 0, 1, NULL);
297 tf = imap_fetch(&connpri, tok, opts.peek ?
298 "BODY.PEEK[TEXT]" : "BODY[TEXT]");
299 do {
300 r = response_fetch(&connpri, tf, 0, buf);
301 if (r != RESPONSE_NULLBODY)
302 socket_write(&connaux, buf);
303 } while (r == RESPONSE_NONE);
304
305 if (r != RESPONSE_NULLBODY)
306 socket_write(&connaux, "\r\n\r\n");
307 else
308 socket_write(&connaux, "\r\n");
309
310 response_append(&connaux, ta);
311
312 tok = strtok_r(NULL, " ", &m);
313 }
314
315 logout(&connaux);
316
317 action_list(mesgs, args);
318
319 xfree(mcp);
320
321 return 0;
322 }
323
324
325 /*
326 * Move messages to the specified mailbox of another mail server.
327 */
328 int
329 action_rmove(char *mbox, char *mesgs, account_t * destacct, char *destmbox,
330 char *args)
331 {
332
333 if (!action_rcopy(mbox, mesgs, destacct, destmbox, args))
334 action_delete(mesgs, "\0");
335
336 return 0;
337 }
338
339
340 /*
341 * Flag messages by replacing, adding or removing specified flags.
342 */
343 int
344 action_flag(char *mesgs, unsigned int *type, unsigned int *msgflags,
345 char *args)
346 {
347 char s[STORE_FLAGS_BUF];
348 char *tok, *m, *mcp;
349
350 *s = 0;
351
352 if ((*msgflags != MSG_FLAG_NONE)) {
353 if ((*msgflags & MSG_FLAG_SEEN))
354 strncat(s, "\\Seen ", STORE_FLAGS_BUF - strlen(s) - 1);
355 if ((*msgflags & MSG_FLAG_ANSWERED))
356 strncat(s, "\\Answered ",
357 STORE_FLAGS_BUF - strlen(s) - 1);
358 if ((*msgflags & MSG_FLAG_FLAGGED))
359 strncat(s, "\\Flagged ",
360 STORE_FLAGS_BUF - strlen(s) - 1);
361 if ((*msgflags & MSG_FLAG_DELETED))
362 strncat(s, "\\Deleted ",
363 STORE_FLAGS_BUF - strlen(s) - 1);
364 if ((*msgflags & MSG_FLAG_DRAFT))
365 strncat(s, "\\Draft", STORE_FLAGS_BUF - strlen(s) - 1);
366 if ((s[strlen(s) - 1] == ' '))
367 s[strlen(s) - 1] = 0;
368 }
369 action_list(mesgs, args);
370
371 m = mcp = convert_messages(mesgs);
372
373 tok = strtok_r(m, " ", &m);
374 while (tok != NULL) {
375 response_generic(&connpri, imap_store(&connpri, tok, *type, s));
376 tok = strtok_r(NULL, " ", &m);
377 }
378
379 if (opts.expunge)
380 response_generic(&connpri, imap_expunge(&connpri));
381
382 xfree(mcp);
383
384 return 0;
385 }
386
387 /*
388 * List user selected headers of messages.
389 */
390 int
391 action_list(char *mesgs, char *args)
392 {
393 int r, t;
394 char *tok, *mcp, *m;
395 char s[ARGS_LEN + 27];
396 char hdrs[RESPONSE_BUF * 2 + 1];
397
398 if (*args == '\0')
399 return 0;
400
401 m = mcp = xstrdup(mesgs);
402
403 snprintf(s, ARGS_LEN + 27 - 1, opts.peek ?
404 "BODY.PEEK[HEADER.FIELDS (%s)]" :
405 "BODY[HEADER.FIELDS (%s)]", args);
406
407 tok = strtok_r(m, " ", &m);
408 while (tok != NULL) {
409 /* Reset internal fetch counter. */
410 response_fetch(&connpri, 0, 1, NULL);
411 t = imap_fetch(&connpri, tok, s);
412
413 log_info(LOG_PREAMBLE, NULL);
414 do {
415 r = response_fetch(&connpri, t, 0, hdrs);
416
417 if (*hdrs != '\0') {
418 if (opts.headers)
419 info("%s\n", hdrs);
420 log_info(LOG_HEADER, hdrs);
421 }
422 } while (r == RESPONSE_NONE);
423
424 tok = strtok_r(NULL, " ", &m);
425 }
426
427 xfree(mcp);
428
429 return 0;
430 }
431
432
433 /*
434 * Count how many messages matched the filter.
435 */
436 unsigned int
437 count_messages(char *mesgs)
438 {
439 unsigned int cnt;
440 char *c;
441
442 cnt = 0;
443 c = mesgs;
444
445 while ((c = strchr(c, ' '))) {
446 cnt++;
447 c++;
448 }
449
450 return ++cnt;
451 }
452
453
454 /*
455 * Convert messages with contiguous sequence number to the corresponding number
456 * range, eg. 1 2 3 5 7 8 --> 1:3 5 7:8 and return a newly allocated buffer
457 * with the results.
458 */
459 char *
460 convert_messages(char *mesgs)
461 {
462 int maxlen;
463 unsigned int start, end, tmp;
464 char *c, *cp, *tail;
465
466 start = end = tmp = 0;
467 maxlen = strlen(mesgs) + 1;
468 tail = NULL;
469
470 c = cp = xstrdup(mesgs);
471
472 start = (unsigned int)strtoul(mesgs, &tail, 10);
473 end = start;
474
475 do {
476 if (tail != NULL) {
477 tmp = (unsigned int)strtoul(tail, &tail, 10);
478 if (tmp == 0)
479 tail = NULL;
480 }
481 if (tmp == end + 1)
482 end++;
483 else {
484 if (start == end) {
485 xstrncpy(c, ultostr(start, 10), maxlen);
486 c += strlen(c);
487 } else {
488 xstrncpy(c, ultostr(start, 10), maxlen - 1);
489 c += strlen(c);
490 *c = ':';
491 *++c = 0;
492 xstrncpy(c, ultostr(end, 10), maxlen);
493 c += strlen(c);
494 }
495
496 if (tail != NULL && c - cp < maxlen) {
497 *c = ' ';
498 *++c = 0;
499 }
500 start = end = tmp;
501 }
502 } while (tmp != 0);
503
504 return cp;
505 }
506
507
508 /*
509 * Substitute all occurences of the '@' character with the '%' character, and
510 * any double '@' characters with a signle '@' character, returning the number
511 * of replacements.
512 */
513 int
514 substitute_date(char *str)
515 {
516 int n;
517 char *r, *w, *s;
518
519 n = 0;
520
521 s = xstrdup(str);
522
523 for (r = s, w = str; *r != '\0'; r++, w++) {
524 if (*r == '%') {
525 *w++ = '%';
526 *w = '%';
527 } else if (*r == '@') {
528 if (*(r + 1) == '@') {
529 *w = *r++;
530 } else {
531 *w = '%';
532 n++;
533 }
534 } else {
535 *w = *r;
536 }
537 }
538 *w = '\0';
539
540 xfree(s);
541
542 return n;
543 }
544
545
546 /*
547 * Format the destination mailbox according to the current date conversion
548 * specifiers.
549 */
550 void
551 system_date(char *destmbox)
552 {
553 char s[MBOX_LEN];
554 time_t te;
555 struct tm *tl;
556
557 if (!strchr(destmbox, '%'))
558 return;
559
560 te = time(NULL);
561 tl = localtime(&te);
562
563 if (strftime(s, MBOX_LEN - 1, destmbox, tl))
564 xstrncpy(destmbox, s, MBOX_LEN - 1);
565 }
566
567
568 /*
569 * Format the destination mailbox according to the message date conversion
570 * specifiers.
571 */
572 void
573 message_date(char *mesg, char *destmbox)
574 {
575 unsigned int t;
576 char s[MBOX_LEN];
577 struct tm tl;
578 char buf[RESPONSE_BUF + 1];
579
580 if (!strchr(destmbox, '@'))
581 return;
582
583 substitute_date(destmbox);
584
585 response_fetch(&connpri, 0, 1, NULL);
586 t = imap_fetch(&connpri, mesg, opts.peek ?
587 "BODY.PEEK[HEADER.FIELDS (DATE)]" :
588 "BODY[HEADER.FIELDS (DATE)]");
589
590 while (response_fetch(&connpri, t, 0, buf) == RESPONSE_NONE);
591
592 if (((strptime(buf, "Date: %a, %d %b %Y %H:%M:%S", &tl) ||
593 strptime(buf, "Date: %d %b %Y %H:%M:%S", &tl)) &&
594 strftime(s, MBOX_LEN - 1, destmbox, &tl)))
595 xstrncpy(destmbox, s, MBOX_LEN - 1);
596 }
597
598
599 /*
600 * Format the destination mailbox according to "default variables" specifiers.
601 */
602 void
603 default_variables(char *mbox, char *destmbox)
604 {
605 char *m, *r, *w, *s;
606
607 if (!strchr(destmbox, '$'))
608 return;
609
610 s = xstrdup(destmbox);
611
612 for (r = s, w = destmbox; *r != '\0';) {
613 if (w - destmbox >= MBOX_LEN - 1)
614 break;
615 if (*r == '$') {
616 switch (*(r + 1)) {
617 case '_':
618 if (w + strlen(mbox) - destmbox >
619 MBOX_LEN - 1) {
620 r += 2;
621 break;
622 }
623 for (m = mbox; *m != '\0'; m++, w++)
624 *w = *m;
625 r += 2;
626 break;
627 case '$':
628 *w++ = '$';
629 r += 2;
630 break;
631 default:
632 *w++ = *r++;
633 break;
634 }
635 } else {
636 *w++ = *r++;
637 }
638 }
639 *w = '\0';
640
641 xfree(s);
642 }
643
644
645 /*
646 * Format the destination mailbox according to "headers variables" specifiers.
647 */
648 void
649 headers_variables(char *mesg, char *destmbox)
650 {
651 int i;
652 unsigned int t;
653 char *c, *r, *w, *s;
654 regex_t creg[2];
655 regmatch_t match[3];
656 char buf[RESPONSE_BUF + 1];
657 struct {
658 char *s;
659 char *e;
660 } user, domain;
661 const char *reg[2] = {
662 "From:[[:blank:]]+([[:graph:]]+)@([[:graph:]]+)"
663 "[[:blank:]]*\r\n",
664
665 "From:[[:print:]]+<([[:graph:]]+)@([[:graph:]]+)>"
666 "[[:blank:]]*\r\n"
667 };
668
669 if (!strchr(destmbox, '&'))
670 return;
671
672 response_fetch(&connpri, 0, 1, NULL);
673 t = imap_fetch(&connpri, mesg, opts.peek ?
674 "BODY.PEEK[HEADER.FIELDS (FROM)]" :
675 "BODY[HEADER.FIELDS (FROM)]");
676 while (response_fetch(&connpri, t, 0, buf) == RESPONSE_NONE);
677
678 for (i = 0; i < 2; i++)
679 regcomp(&creg[i], reg[i], REG_EXTENDED | REG_ICASE);
680
681 if (!regexec(&creg[0], buf, 3, match, 0) ||
682 !regexec(&creg[1], buf, 3, match, 0)) {
683 user.s = buf + match[1].rm_so;
684 user.e = buf + match[1].rm_eo;
685 domain.s = buf + match[2].rm_so;
686 domain.e = buf + match[2].rm_eo;
687 } else {
688 for (i = 0; i < 2; i++)
689 regfree(&creg[i]);
690 return;
691 }
692
693 s = xstrdup(destmbox);
694
695 for (r = s, w = destmbox; *r != '\0';) {
696 if (w - destmbox >= MBOX_LEN - 1)
697 break;
698 if (*r == '&') {
699 switch (*(r + 1)) {
700 case 'f':
701 if (w + (user.e - user.s) +
702 (domain.s - domain.e) + 1 -
703 destmbox > MBOX_LEN) {
704 r += 2;
705 break;
706 }
707 for (c = user.s; c < user.e; c++, w++)
708 *w = *c;
709 *w++ = '@';
710 for (c = domain.s; c < domain.e; c++, w++)
711 *w = *c;
712 r += 2;
713 break;
714 case 'u':
715 if (w + (user.e - user.s) - destmbox >
716 MBOX_LEN - 1) {
717 r += 2;
718 break;
719 }
720 for (c = user.s; c < user.e; c++, w++)
721 *w = *c;
722 r += 2;
723 break;
724 case 'd':
725 if (w + (domain.e - domain.s) - destmbox >
726 MBOX_LEN - 1) {
727 r += 2;
728 break;
729 }
730 for (c = domain.s; c < domain.e; c++, w++)
731 *w = *c;
732 r += 2;
733 break;
734 case '&':
735 *w++ = '&';
736 r += 2;
737 break;
738 default:
739 *w++ = *r++;
740 break;
741 }
742 } else {
743 *w++ = *r++;
744 }
745 }
746 *w = '\0';
747
748 for (i = 0; i < 2; i++)
749 regfree(&creg[i]);
750 xfree(s);
751 }

webmaster@linux.gr
ViewVC Help
Powered by ViewVC 1.1.26