remove the duplicate for 'allheaders'
[citadel.git] / citadel / modules / sieve / serv_sieve.c
1 /*
2  * This module glues libSieve to the Citadel server in order to implement
3  * the Sieve mailbox filtering language (RFC 3028).
4  *
5  * Copyright (c) 1987-2010 by the citadel.org team
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "sysdep.h"
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <ctype.h>
28 #include <pwd.h>
29 #include <errno.h>
30 #include <sys/types.h>
31
32 #if TIME_WITH_SYS_TIME
33 # include <sys/time.h>
34 # include <time.h>
35 #else
36 # if HAVE_SYS_TIME_H
37 #  include <sys/time.h>
38 # else
39 #  include <time.h>
40 # endif
41 #endif
42
43 #include <sys/wait.h>
44 #include <string.h>
45 #include <limits.h>
46 #include <libcitadel.h>
47 #include "citadel.h"
48 #include "server.h"
49 #include "citserver.h"
50 #include "support.h"
51 #include "config.h"
52 #include "database.h"
53 #include "msgbase.h"
54 #include "internet_addressing.h"
55 #include "ctdl_module.h"
56 #include "serv_sieve.h"
57
58 struct RoomProcList *sieve_list = NULL;
59 char *msiv_extensions = NULL;
60
61
62 /*
63  * Callback function to send libSieve trace messages to Citadel log facility
64  */
65 int ctdl_debug(sieve2_context_t *s, void *my)
66 {
67         syslog(LOG_DEBUG, "Sieve: %s\n", sieve2_getvalue_string(s, "message"));
68         return SIEVE2_OK;
69 }
70
71
72 /*
73  * Callback function to log script parsing errors
74  */
75 int ctdl_errparse(sieve2_context_t *s, void *my)
76 {
77         syslog(LOG_WARNING, "Error in script, line %d: %s\n",
78                 sieve2_getvalue_int(s, "lineno"),
79                 sieve2_getvalue_string(s, "message")
80         );
81         return SIEVE2_OK;
82 }
83
84
85 /*
86  * Callback function to log script execution errors
87  */
88 int ctdl_errexec(sieve2_context_t *s, void *my)
89 {
90         syslog(LOG_WARNING, "Error executing script: %s\n",
91                 sieve2_getvalue_string(s, "message")
92         );
93         return SIEVE2_OK;
94 }
95
96
97 /*
98  * Callback function to redirect a message to a different folder
99  */
100 int ctdl_redirect(sieve2_context_t *s, void *my)
101 {
102         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
103         struct CtdlMessage *msg = NULL;
104         struct recptypes *valid = NULL;
105         char recp[256];
106
107         safestrncpy(recp, sieve2_getvalue_string(s, "address"), sizeof recp);
108
109         syslog(LOG_DEBUG, "Action is REDIRECT, recipient <%s>\n", recp);
110
111         valid = validate_recipients(recp, NULL, 0);
112         if (valid == NULL) {
113                 syslog(LOG_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
114                 return SIEVE2_ERROR_BADARGS;
115         }
116         if (valid->num_error > 0) {
117                 syslog(LOG_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
118                 free_recipients(valid);
119                 return SIEVE2_ERROR_BADARGS;
120         }
121
122         msg = CtdlFetchMessage(cs->msgnum, 1);
123         if (msg == NULL) {
124                 syslog(LOG_WARNING, "REDIRECT failed: unable to fetch msg %ld\n", cs->msgnum);
125                 free_recipients(valid);
126                 return SIEVE2_ERROR_BADARGS;
127         }
128
129         CtdlSubmitMsg(msg, valid, NULL, 0);
130         cs->cancel_implicit_keep = 1;
131         free_recipients(valid);
132         CtdlFreeMessage(msg);
133         return SIEVE2_OK;
134 }
135
136
137 /*
138  * Callback function to indicate that a message *will* be kept in the inbox
139  */
140 int ctdl_keep(sieve2_context_t *s, void *my)
141 {
142         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
143         
144         syslog(LOG_DEBUG, "Action is KEEP\n");
145
146         cs->keep = 1;
147         cs->cancel_implicit_keep = 1;
148         return SIEVE2_OK;
149 }
150
151
152 /*
153  * Callback function to file a message into a different mailbox
154  */
155 int ctdl_fileinto(sieve2_context_t *s, void *my)
156 {
157         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
158         const char *dest_folder = sieve2_getvalue_string(s, "mailbox");
159         int c;
160         char foldername[256];
161         char original_room_name[ROOMNAMELEN];
162
163         syslog(LOG_DEBUG, "Action is FILEINTO, destination is <%s>\n", dest_folder);
164
165         /* FILEINTO 'INBOX' is the same thing as KEEP */
166         if ( (!strcasecmp(dest_folder, "INBOX")) || (!strcasecmp(dest_folder, MAILROOM)) ) {
167                 cs->keep = 1;
168                 cs->cancel_implicit_keep = 1;
169                 return SIEVE2_OK;
170         }
171
172         /* Remember what room we came from */
173         safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
174
175         /* First try a mailbox name match (check personal mail folders first) */
176         snprintf(foldername, sizeof foldername, "%010ld.%s", cs->usernum, dest_folder);
177         c = CtdlGetRoom(&CC->room, foldername);
178
179         /* Then a regular room name match (public and private rooms) */
180         if (c != 0) {
181                 safestrncpy(foldername, dest_folder, sizeof foldername);
182                 c = CtdlGetRoom(&CC->room, foldername);
183         }
184
185         if (c != 0) {
186                 syslog(LOG_WARNING, "FILEINTO failed: target <%s> does not exist\n", dest_folder);
187                 return SIEVE2_ERROR_BADARGS;
188         }
189
190         /* Yes, we actually have to go there */
191         CtdlUserGoto(NULL, 0, 0, NULL, NULL);
192
193         c = CtdlSaveMsgPointersInRoom(NULL, &cs->msgnum, 1, 0, NULL, 0);
194
195         /* Go back to the room we came from */
196         if (strcasecmp(original_room_name, CC->room.QRname)) {
197                 CtdlUserGoto(original_room_name, 0, 0, NULL, NULL);
198         }
199
200         if (c == 0) {
201                 cs->cancel_implicit_keep = 1;
202                 return SIEVE2_OK;
203         }
204         else {
205                 return SIEVE2_ERROR_BADARGS;
206         }
207 }
208
209
210 /*
211  * Callback function to indicate that a message should be discarded.
212  */
213 int ctdl_discard(sieve2_context_t *s, void *my)
214 {
215         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
216
217         syslog(LOG_DEBUG, "Action is DISCARD\n");
218
219         /* Cancel the implicit keep.  That's all there is to it. */
220         cs->cancel_implicit_keep = 1;
221         return SIEVE2_OK;
222 }
223
224
225
226 /*
227  * Callback function to indicate that a message should be rejected
228  */
229 int ctdl_reject(sieve2_context_t *s, void *my)
230 {
231         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
232         char *reject_text = NULL;
233
234         syslog(LOG_DEBUG, "Action is REJECT\n");
235
236         /* If we don't know who sent the message, do a DISCARD instead. */
237         if (IsEmptyStr(cs->sender)) {
238                 syslog(LOG_INFO, "Unknown sender.  Doing DISCARD instead of REJECT.\n");
239                 return ctdl_discard(s, my);
240         }
241
242         /* Assemble the reject message. */
243         reject_text = malloc(strlen(sieve2_getvalue_string(s, "message")) + 1024);
244         if (reject_text == NULL) {
245                 return SIEVE2_ERROR_FAIL;
246         }
247
248         sprintf(reject_text, 
249                 "Content-type: text/plain\n"
250                 "\n"
251                 "The message was refused by the recipient's mail filtering program.\n"
252                 "The reason given was as follows:\n"
253                 "\n"
254                 "%s\n"
255                 "\n"
256         ,
257                 sieve2_getvalue_string(s, "message")
258         );
259
260         quickie_message(        /* This delivers the message */
261                 NULL,
262                 cs->envelope_to,
263                 cs->sender,
264                 NULL,
265                 reject_text,
266                 FMT_RFC822,
267                 "Delivery status notification"
268         );
269
270         free(reject_text);
271         cs->cancel_implicit_keep = 1;
272         return SIEVE2_OK;
273 }
274
275
276
277 /*
278  * Callback function to indicate that a vacation message should be generated
279  */
280 int ctdl_vacation(sieve2_context_t *s, void *my)
281 {
282         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
283         struct sdm_vacation *vptr;
284         int days = 1;
285         const char *message;
286         char *vacamsg_text = NULL;
287         char vacamsg_subject[1024];
288
289         syslog(LOG_DEBUG, "Action is VACATION\n");
290
291         message = sieve2_getvalue_string(s, "message");
292         if (message == NULL) return SIEVE2_ERROR_BADARGS;
293
294         if (sieve2_getvalue_string(s, "subject") != NULL) {
295                 safestrncpy(vacamsg_subject, sieve2_getvalue_string(s, "subject"), sizeof vacamsg_subject);
296         }
297         else {
298                 snprintf(vacamsg_subject, sizeof vacamsg_subject, "Re: %s", cs->subject);
299         }
300
301         days = sieve2_getvalue_int(s, "days");
302         if (days < 1) days = 1;
303         if (days > MAX_VACATION) days = MAX_VACATION;
304
305         /* Check to see whether we've already alerted this sender that we're on vacation. */
306         for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
307                 if (!strcasecmp(vptr->fromaddr, cs->sender)) {
308                         if ( (time(NULL) - vptr->timestamp) < (days * 86400) ) {
309                                 syslog(LOG_DEBUG, "Already alerted <%s> recently.\n", cs->sender);
310                                 return SIEVE2_OK;
311                         }
312                 }
313         }
314
315         /* Assemble the reject message. */
316         vacamsg_text = malloc(strlen(message) + 1024);
317         if (vacamsg_text == NULL) {
318                 return SIEVE2_ERROR_FAIL;
319         }
320
321         sprintf(vacamsg_text, 
322                 "Content-type: text/plain charset=utf-8\n"
323                 "\n"
324                 "%s\n"
325                 "\n"
326         ,
327                 message
328         );
329
330         quickie_message(        /* This delivers the message */
331                 NULL,
332                 cs->envelope_to,
333                 cs->sender,
334                 NULL,
335                 vacamsg_text,
336                 FMT_RFC822,
337                 vacamsg_subject
338         );
339
340         free(vacamsg_text);
341
342         /* Now update the list to reflect the fact that we've alerted this sender.
343          * If they're already in the list, just update the timestamp.
344          */
345         for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
346                 if (!strcasecmp(vptr->fromaddr, cs->sender)) {
347                         vptr->timestamp = time(NULL);
348                         return SIEVE2_OK;
349                 }
350         }
351
352         /* If we get to this point, create a new record.
353          */
354         vptr = malloc(sizeof(struct sdm_vacation));
355         memset(vptr, 0, sizeof(struct sdm_vacation));
356         vptr->timestamp = time(NULL);
357         safestrncpy(vptr->fromaddr, cs->sender, sizeof vptr->fromaddr);
358         vptr->next = cs->u->first_vacation;
359         cs->u->first_vacation = vptr;
360
361         return SIEVE2_OK;
362 }
363
364
365 /*
366  * Callback function to parse addresses per local system convention
367  * It is disabled because we don't support subaddresses.
368  */
369 #if 0
370 int ctdl_getsubaddress(sieve2_context_t *s, void *my)
371 {
372         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
373
374         /* libSieve does not take ownership of the memory used here.  But, since we
375          * are just pointing to locations inside a struct which we are going to free
376          * later, we're ok.
377          */
378         sieve2_setvalue_string(s, "user", cs->recp_user);
379         sieve2_setvalue_string(s, "detail", "");
380         sieve2_setvalue_string(s, "localpart", cs->recp_user);
381         sieve2_setvalue_string(s, "domain", cs->recp_node);
382         return SIEVE2_OK;
383 }
384 #endif
385
386
387 /*
388  * Callback function to parse message envelope
389  */
390 int ctdl_getenvelope(sieve2_context_t *s, void *my)
391 {
392         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
393
394         syslog(LOG_DEBUG, "Action is GETENVELOPE\nEnvFrom: %s\n  EnvTo: %s\n",
395                 cs->envelope_from, cs->envelope_to);
396
397         if (cs->envelope_from != NULL) {
398                 if ((cs->envelope_from[0] != '@')&&(cs->envelope_from[strlen(cs->envelope_from)-1] != '@')) {
399                         sieve2_setvalue_string(s, "from", cs->envelope_from);
400                 }
401                 else {
402                         sieve2_setvalue_string(s, "from", "invalid_envelope_from@example.org");
403                 }
404         }
405         else {
406                 sieve2_setvalue_string(s, "from", "null_envelope_from@example.org");
407         }
408
409
410         if (cs->envelope_to != NULL) {
411                 if ((cs->envelope_to[0] != '@') && (cs->envelope_to[strlen(cs->envelope_to)-1] != '@')) {
412                         sieve2_setvalue_string(s, "to", cs->envelope_to);
413                 }
414                 else {
415                         sieve2_setvalue_string(s, "to", "invalid_envelope_to@example.org");
416                 }
417         }
418         else {
419                 sieve2_setvalue_string(s, "to", "null_envelope_to@example.org");
420         }
421
422         return SIEVE2_OK;
423 }
424
425
426 /*
427  * Callback function to fetch message body
428  * (Uncomment the code if we implement this extension)
429  *
430 int ctdl_getbody(sieve2_context_t *s, void *my)
431 {
432         return SIEVE2_ERROR_UNSUPPORTED;
433 }
434  *
435  */
436
437
438 /*
439  * Callback function to fetch message size
440  */
441 int ctdl_getsize(sieve2_context_t *s, void *my)
442 {
443         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
444         struct MetaData smi;
445
446         GetMetaData(&smi, cs->msgnum);
447         
448         if (smi.meta_rfc822_length > 0L) {
449                 sieve2_setvalue_int(s, "size", (int)smi.meta_rfc822_length);
450                 return SIEVE2_OK;
451         }
452
453         return SIEVE2_ERROR_UNSUPPORTED;
454 }
455
456
457 /*
458  * Callback function to retrieve the sieve script
459  */
460 int ctdl_getscript(sieve2_context_t *s, void *my) {
461         struct sdm_script *sptr;
462         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
463
464         for (sptr=cs->u->first_script; sptr!=NULL; sptr=sptr->next) {
465                 if (sptr->script_active > 0) {
466                         syslog(LOG_DEBUG, "ctdl_getscript() is using script '%s'\n", sptr->script_name);
467                         sieve2_setvalue_string(s, "script", sptr->script_content);
468                         return SIEVE2_OK;
469                 }
470         }
471                 
472         syslog(LOG_DEBUG, "ctdl_getscript() found no active script\n");
473         return SIEVE2_ERROR_GETSCRIPT;
474 }
475
476 /*
477  * Callback function to retrieve message headers
478  */
479 int ctdl_getheaders(sieve2_context_t *s, void *my) {
480
481         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
482
483         syslog(LOG_DEBUG, "ctdl_getheaders() was called\n");
484         sieve2_setvalue_string(s, "allheaders", cs->rfc822headers);
485         return SIEVE2_OK;
486 }
487
488
489
490 /*
491  * Add a room to the list of those rooms which potentially require sieve processing
492  */
493 void sieve_queue_room(struct ctdlroom *which_room) {
494         struct RoomProcList *ptr;
495
496         ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
497         if (ptr == NULL) return;
498
499         safestrncpy(ptr->name, which_room->QRname, sizeof ptr->name);
500         begin_critical_section(S_SIEVELIST);
501         ptr->next = sieve_list;
502         sieve_list = ptr;
503         end_critical_section(S_SIEVELIST);
504         syslog(LOG_DEBUG, "<%s> queued for Sieve processing\n", which_room->QRname);
505 }
506
507
508
509 /*
510  * Perform sieve processing for one message (called by sieve_do_room() for each message)
511  */
512 void sieve_do_msg(long msgnum, void *userdata) {
513         struct sdm_userdata *u = (struct sdm_userdata *) userdata;
514         sieve2_context_t *sieve2_context;
515         struct ctdl_sieve my;
516         int res;
517         struct CtdlMessage *msg;
518         int i;
519         size_t headers_len = 0;
520         int len = 0;
521
522         if (u == NULL)
523         {
524                 syslog(LOG_EMERG, "Can't process message <%ld> without userdata!\n", msgnum);
525                 return;
526         }
527
528         sieve2_context = u->sieve2_context;
529
530         syslog(LOG_DEBUG, "Performing sieve processing on msg <%ld>\n", msgnum);
531
532         /*
533          * Make sure you include message body so you can get those second-level headers ;)
534          */
535         msg = CtdlFetchMessage(msgnum, 1);
536         if (msg == NULL) return;
537
538         /*
539          * Grab the message headers so we can feed them to libSieve.
540          * Use HEADERS_ONLY rather than HEADERS_FAST in order to include second-level headers.
541          */
542         CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
543         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, 0);
544         headers_len = StrLength(CC->redirect_buffer);
545         my.rfc822headers = SmashStrBuf(&CC->redirect_buffer);
546
547         /*
548          * libSieve clobbers the stack if it encounters badly formed
549          * headers.  Sanitize our headers by stripping nonprintable
550          * characters.
551          */
552         for (i=0; i<headers_len; ++i) {
553                 if (!isascii(my.rfc822headers[i])) {
554                         my.rfc822headers[i] = '_';
555                 }
556         }
557
558         my.keep = 0;                            /* Set to 1 to declare an *explicit* keep */
559         my.cancel_implicit_keep = 0;            /* Some actions will cancel the implicit keep */
560         my.usernum = atol(CC->room.QRname);     /* Keep track of the owner of the room's namespace */
561         my.msgnum = msgnum;                     /* Keep track of the message number in our local store */
562         my.u = u;                               /* Hand off a pointer to the rest of this info */
563
564         /* Keep track of the recipient so we can do handling based on it later */
565         process_rfc822_addr(msg->cm_fields['R'], my.recp_user, my.recp_node, my.recp_name);
566
567         /* Keep track of the sender so we can use it for REJECT and VACATION responses */
568         if (msg->cm_fields['F'] != NULL) {
569                 safestrncpy(my.sender, msg->cm_fields['F'], sizeof my.sender);
570         }
571         else if ( (msg->cm_fields['A'] != NULL) && (msg->cm_fields['N'] != NULL) ) {
572                 snprintf(my.sender, sizeof my.sender, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
573         }
574         else if (msg->cm_fields['A'] != NULL) {
575                 safestrncpy(my.sender, msg->cm_fields['A'], sizeof my.sender);
576         }
577         else {
578                 strcpy(my.sender, "");
579         }
580
581         /* Keep track of the subject so we can use it for VACATION responses */
582         if (msg->cm_fields['U'] != NULL) {
583                 safestrncpy(my.subject, msg->cm_fields['U'], sizeof my.subject);
584         }
585         else {
586                 strcpy(my.subject, "");
587         }
588
589         /* Keep track of the envelope-from address (use body-from if not found) */
590         if (msg->cm_fields['P'] != NULL) {
591                 safestrncpy(my.envelope_from, msg->cm_fields['P'], sizeof my.envelope_from);
592                 stripallbut(my.envelope_from, '<', '>');
593         }
594         else if (msg->cm_fields['F'] != NULL) {
595                 safestrncpy(my.envelope_from, msg->cm_fields['F'], sizeof my.envelope_from);
596                 stripallbut(my.envelope_from, '<', '>');
597         }
598         else {
599                 strcpy(my.envelope_from, "");
600         }
601
602         len = strlen(my.envelope_from);
603         for (i=0; i<len; ++i) {
604                 if (isspace(my.envelope_from[i])) my.envelope_from[i] = '_';
605         }
606         if (haschar(my.envelope_from, '@') == 0) {
607                 strcat(my.envelope_from, "@");
608                 strcat(my.envelope_from, config.c_fqdn);
609         }
610
611         /* Keep track of the envelope-to address (use body-to if not found) */
612         if (msg->cm_fields['V'] != NULL) {
613                 safestrncpy(my.envelope_to, msg->cm_fields['V'], sizeof my.envelope_to);
614                 stripallbut(my.envelope_to, '<', '>');
615         }
616         else if (msg->cm_fields['R'] != NULL) {
617                 safestrncpy(my.envelope_to, msg->cm_fields['R'], sizeof my.envelope_to);
618                 if (msg->cm_fields['D'] != NULL) {
619                         strcat(my.envelope_to, "@");
620                         strcat(my.envelope_to, msg->cm_fields['D']);
621                 }
622                 stripallbut(my.envelope_to, '<', '>');
623         }
624         else {
625                 strcpy(my.envelope_to, "");
626         }
627
628         len = strlen(my.envelope_to);
629         for (i=0; i<len; ++i) {
630                 if (isspace(my.envelope_to[i])) my.envelope_to[i] = '_';
631         }
632         if (haschar(my.envelope_to, '@') == 0) {
633                 strcat(my.envelope_to, "@");
634                 strcat(my.envelope_to, config.c_fqdn);
635         }
636
637         CtdlFreeMessage(msg);
638         
639         syslog(LOG_DEBUG, "Calling sieve2_execute()\n");
640         res = sieve2_execute(sieve2_context, &my);
641         if (res != SIEVE2_OK) {
642                 syslog(LOG_CRIT, "sieve2_execute() returned %d: %s\n", res, sieve2_errstr(res));
643         }
644
645         free(my.rfc822headers);
646         my.rfc822headers = NULL;
647
648         /*
649          * Delete the message from the inbox unless either we were told not to, or
650          * if no other action was successfully taken.
651          */
652         if ( (!my.keep) && (my.cancel_implicit_keep) ) {
653                 syslog(LOG_DEBUG, "keep is 0 -- deleting message from inbox\n");
654                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
655         }
656
657         syslog(LOG_DEBUG, "Completed sieve processing on msg <%ld>\n", msgnum);
658         u->lastproc = msgnum;
659
660         return;
661 }
662
663
664
665 /*
666  * Given the on-disk representation of our Sieve config, load
667  * it into an in-memory data structure.
668  */
669 void parse_sieve_config(char *conf, struct sdm_userdata *u) {
670         char *ptr;
671         char *c, *vacrec;
672         char keyword[256];
673         struct sdm_script *sptr;
674         struct sdm_vacation *vptr;
675
676         ptr = conf;
677         while (c = ptr, ptr = bmstrcasestr(ptr, CTDLSIEVECONFIGSEPARATOR), ptr != NULL) {
678                 *ptr = 0;
679                 ptr += strlen(CTDLSIEVECONFIGSEPARATOR);
680
681                 extract_token(keyword, c, 0, '|', sizeof keyword);
682
683                 if (!strcasecmp(keyword, "lastproc")) {
684                         u->lastproc = extract_long(c, 1);
685                 }
686
687                 else if (!strcasecmp(keyword, "script")) {
688                         sptr = malloc(sizeof(struct sdm_script));
689                         extract_token(sptr->script_name, c, 1, '|', sizeof sptr->script_name);
690                         sptr->script_active = extract_int(c, 2);
691                         remove_token(c, 0, '|');
692                         remove_token(c, 0, '|');
693                         remove_token(c, 0, '|');
694                         sptr->script_content = strdup(c);
695                         sptr->next = u->first_script;
696                         u->first_script = sptr;
697                 }
698
699                 else if (!strcasecmp(keyword, "vacation")) {
700
701                         if (c != NULL) while (vacrec=c, c=strchr(c, '\n'), (c != NULL)) {
702
703                                 *c = 0;
704                                 ++c;
705
706                                 if (strncasecmp(vacrec, "vacation|", 9)) {
707                                         vptr = malloc(sizeof(struct sdm_vacation));
708                                         extract_token(vptr->fromaddr, vacrec, 0, '|', sizeof vptr->fromaddr);
709                                         vptr->timestamp = extract_long(vacrec, 1);
710                                         vptr->next = u->first_vacation;
711                                         u->first_vacation = vptr;
712                                 }
713                         }
714                 }
715
716                 /* ignore unknown keywords */
717         }
718 }
719
720 /*
721  * We found the Sieve configuration for this user.
722  * Now do something with it.
723  */
724 void get_sieve_config_backend(long msgnum, void *userdata) {
725         struct sdm_userdata *u = (struct sdm_userdata *) userdata;
726         struct CtdlMessage *msg;
727         char *conf;
728
729         u->config_msgnum = msgnum;
730         msg = CtdlFetchMessage(msgnum, 1);
731         if (msg == NULL) {
732                 u->config_msgnum = (-1) ;
733                 return;
734         }
735
736         conf = msg->cm_fields['M'];
737         msg->cm_fields['M'] = NULL;
738         CtdlFreeMessage(msg);
739
740         if (conf != NULL) {
741                 parse_sieve_config(conf, u);
742                 free(conf);
743         }
744
745 }
746
747
748 /* 
749  * Write our citadel sieve config back to disk
750  * 
751  * (Set yes_write_to_disk to nonzero to make it actually write the config;
752  * otherwise it just frees the data structures.)
753  */
754 void rewrite_ctdl_sieve_config(struct sdm_userdata *u, int yes_write_to_disk) {
755         StrBuf *text;
756         struct sdm_script *sptr;
757         struct sdm_vacation *vptr;
758         
759         text = NewStrBufPlain(NULL, SIZ);
760         StrBufPrintf(text,
761                      "Content-type: application/x-citadel-sieve-config\n"
762                      "\n"
763                      CTDLSIEVECONFIGSEPARATOR
764                      "lastproc|%ld"
765                      CTDLSIEVECONFIGSEPARATOR
766                      ,
767                      u->lastproc
768                 );
769
770         while (u->first_script != NULL) {
771                 StrBufAppendPrintf(text,
772                                    "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR,
773                                    u->first_script->script_name,
774                                    u->first_script->script_active,
775                                    u->first_script->script_content
776                         );
777                 sptr = u->first_script;
778                 u->first_script = u->first_script->next;
779                 free(sptr->script_content);
780                 free(sptr);
781         }
782
783         if (u->first_vacation != NULL) {
784
785                 StrBufAppendPrintf(text, "vacation|\n");
786                 while (u->first_vacation != NULL) {
787                         if ( (time(NULL) - u->first_vacation->timestamp) < (MAX_VACATION * 86400)) {
788                                 StrBufAppendPrintf(text, "%s|%ld\n",
789                                                    u->first_vacation->fromaddr,
790                                                    u->first_vacation->timestamp
791                                         );
792                         }
793                         vptr = u->first_vacation;
794                         u->first_vacation = u->first_vacation->next;
795                         free(vptr);
796                 }
797                 StrBufAppendPrintf(text, CTDLSIEVECONFIGSEPARATOR);
798         }
799
800         if (yes_write_to_disk)
801         {
802                 /* Save the config */
803                 quickie_message("Citadel", NULL, NULL, u->config_roomname,
804                                 ChrPtr(text),
805                                 4,
806                                 "Sieve configuration"
807                 );
808                 
809                 /* And delete the old one */
810                 if (u->config_msgnum > 0) {
811                         CtdlDeleteMessages(u->config_roomname, &u->config_msgnum, 1, "");
812                 }
813         }
814
815         FreeStrBuf (&text);
816
817 }
818
819
820 /*
821  * This is our callback registration table for libSieve.
822  */
823 sieve2_callback_t ctdl_sieve_callbacks[] = {
824         { SIEVE2_ACTION_REJECT,         ctdl_reject             },
825         { SIEVE2_ACTION_VACATION,       ctdl_vacation           },
826         { SIEVE2_ERRCALL_PARSE,         ctdl_errparse           },
827         { SIEVE2_ERRCALL_RUNTIME,       ctdl_errexec            },
828         { SIEVE2_ACTION_FILEINTO,       ctdl_fileinto           },
829         { SIEVE2_ACTION_REDIRECT,       ctdl_redirect           },
830         { SIEVE2_ACTION_DISCARD,        ctdl_discard            },
831         { SIEVE2_ACTION_KEEP,           ctdl_keep               },
832         { SIEVE2_SCRIPT_GETSCRIPT,      ctdl_getscript          },
833         { SIEVE2_DEBUG_TRACE,           ctdl_debug              },
834         { SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders         },
835         { SIEVE2_MESSAGE_GETSIZE,       ctdl_getsize            },
836         { SIEVE2_MESSAGE_GETENVELOPE,   ctdl_getenvelope        },
837 /*
838  * These actions are unsupported by Citadel so we don't declare them.
839  *
840         { SIEVE2_ACTION_NOTIFY,         ctdl_notify             },
841         { SIEVE2_MESSAGE_GETSUBADDRESS, ctdl_getsubaddress      },
842         { SIEVE2_MESSAGE_GETBODY,       ctdl_getbody            },
843  *
844  */
845         { 0 }
846 };
847
848
849 /*
850  * Perform sieve processing for a single room
851  */
852 void sieve_do_room(char *roomname) {
853         
854         struct sdm_userdata u;
855         sieve2_context_t *sieve2_context = NULL;        /* Context for sieve parser */
856         int res;                                        /* Return code from libsieve calls */
857         long orig_lastproc = 0;
858
859         memset(&u, 0, sizeof u);
860
861         /* See if the user who owns this 'mailbox' has any Sieve scripts that
862          * require execution.
863          */
864         snprintf(u.config_roomname, sizeof u.config_roomname, "%010ld.%s", atol(roomname), USERCONFIGROOM);
865         if (CtdlGetRoom(&CC->room, u.config_roomname) != 0) {
866                 syslog(LOG_DEBUG, "<%s> does not exist.  No processing is required.\n", u.config_roomname);
867                 return;
868         }
869
870         /*
871          * Find the sieve scripts and control record and do something
872          */
873         u.config_msgnum = (-1);
874         CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
875                 get_sieve_config_backend, (void *)&u );
876
877         if (u.config_msgnum < 0) {
878                 syslog(LOG_DEBUG, "No Sieve rules exist.  No processing is required.\n");
879                 return;
880         }
881
882         syslog(LOG_DEBUG, "Rules found.  Performing Sieve processing for <%s>\n", roomname);
883
884         if (CtdlGetRoom(&CC->room, roomname) != 0) {
885                 syslog(LOG_CRIT, "ERROR: cannot load <%s>\n", roomname);
886                 return;
887         }
888
889         /* Initialize the Sieve parser */
890         
891         res = sieve2_alloc(&sieve2_context);
892         if (res != SIEVE2_OK) {
893                 syslog(LOG_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
894                 return;
895         }
896
897         res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
898         if (res != SIEVE2_OK) {
899                 syslog(LOG_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
900                 goto BAIL;
901         }
902
903         /* Validate the script */
904
905         struct ctdl_sieve my;           /* dummy ctdl_sieve struct just to pass "u" slong */
906         memset(&my, 0, sizeof my);
907         my.u = &u;
908         res = sieve2_validate(sieve2_context, &my);
909         if (res != SIEVE2_OK) {
910                 syslog(LOG_CRIT, "sieve2_validate() returned %d: %s\n", res, sieve2_errstr(res));
911                 goto BAIL;
912         }
913
914         /* Do something useful */
915         u.sieve2_context = sieve2_context;
916         orig_lastproc = u.lastproc;
917         CtdlForEachMessage(MSGS_GT, u.lastproc, NULL, NULL, NULL,
918                 sieve_do_msg,
919                 (void *) &u
920         );
921
922 BAIL:
923         res = sieve2_free(&sieve2_context);
924         if (res != SIEVE2_OK) {
925                 syslog(LOG_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
926         }
927
928         /* Rewrite the config if we have to */
929         rewrite_ctdl_sieve_config(&u, (u.lastproc > orig_lastproc) ) ;
930 }
931
932
933 /*
934  * Perform sieve processing for all rooms which require it
935  */
936 void perform_sieve_processing(void) {
937         struct RoomProcList *ptr = NULL;
938
939         if (sieve_list != NULL) {
940                 syslog(LOG_DEBUG, "Begin Sieve processing\n");
941                 while (sieve_list != NULL) {
942                         char spoolroomname[ROOMNAMELEN];
943                         safestrncpy(spoolroomname, sieve_list->name, sizeof spoolroomname);
944                         begin_critical_section(S_SIEVELIST);
945
946                         /* pop this record off the list */
947                         ptr = sieve_list;
948                         sieve_list = sieve_list->next;
949                         free(ptr);
950
951                         /* invalidate any duplicate entries to prevent double processing */
952                         for (ptr=sieve_list; ptr!=NULL; ptr=ptr->next) {
953                                 if (!strcasecmp(ptr->name, spoolroomname)) {
954                                         ptr->name[0] = 0;
955                                 }
956                         }
957
958                         end_critical_section(S_SIEVELIST);
959                         if (spoolroomname[0] != 0) {
960                                 sieve_do_room(spoolroomname);
961                         }
962                 }
963         }
964 }
965
966
967 void msiv_load(struct sdm_userdata *u) {
968         char hold_rm[ROOMNAMELEN];
969
970         strcpy(hold_rm, CC->room.QRname);       /* save current room */
971
972         /* Take a spin through the user's personal address book */
973         if (CtdlGetRoom(&CC->room, USERCONFIGROOM) == 0) {
974         
975                 u->config_msgnum = (-1);
976                 strcpy(u->config_roomname, CC->room.QRname);
977                 CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
978                         get_sieve_config_backend, (void *)u );
979
980         }
981
982         if (strcmp(CC->room.QRname, hold_rm)) {
983                 CtdlGetRoom(&CC->room, hold_rm);    /* return to saved room */
984         }
985 }
986
987 void msiv_store(struct sdm_userdata *u, int yes_write_to_disk) {
988 /*
989  * Initialise the sieve configs last processed message number.
990  * We don't need to get the highest message number for the users inbox since the systems
991  * highest message number will be higher than that and loer than this scripts message number
992  * This prevents this new script from processing any old messages in the inbox.
993  * Most importantly it will prevent vacation messages being sent to lots of old messages
994  * in the inbox.
995  */
996         u->lastproc = CtdlGetCurrentMessageNumber();
997         rewrite_ctdl_sieve_config(u, yes_write_to_disk);
998 }
999
1000
1001 /*
1002  * Select the active script.
1003  * (Set script_name to an empty string to disable all scripts)
1004  * 
1005  * Returns 0 on success or nonzero for error.
1006  */
1007 int msiv_setactive(struct sdm_userdata *u, char *script_name) {
1008         int ok = 0;
1009         struct sdm_script *s;
1010
1011         /* First see if the supplied value is ok */
1012
1013         if (IsEmptyStr(script_name)) {
1014                 ok = 1;
1015         }
1016         else {
1017                 for (s=u->first_script; s!=NULL; s=s->next) {
1018                         if (!strcasecmp(s->script_name, script_name)) {
1019                                 ok = 1;
1020                         }
1021                 }
1022         }
1023
1024         if (!ok) return(-1);
1025
1026         /* Now set the active script */
1027         for (s=u->first_script; s!=NULL; s=s->next) {
1028                 if (!strcasecmp(s->script_name, script_name)) {
1029                         s->script_active = 1;
1030                 }
1031                 else {
1032                         s->script_active = 0;
1033                 }
1034         }
1035         
1036         return(0);
1037 }
1038
1039
1040 /*
1041  * Fetch a script by name.
1042  *
1043  * Returns NULL if the named script was not found, or a pointer to the script
1044  * if it was found.   NOTE: the caller does *not* own the memory returned by
1045  * this function.  Copy it if you need to keep it.
1046  */
1047 char *msiv_getscript(struct sdm_userdata *u, char *script_name) {
1048         struct sdm_script *s;
1049
1050         for (s=u->first_script; s!=NULL; s=s->next) {
1051                 if (!strcasecmp(s->script_name, script_name)) {
1052                         if (s->script_content != NULL) {
1053                                 return (s->script_content);
1054                         }
1055                 }
1056         }
1057
1058         return(NULL);
1059 }
1060
1061
1062 /*
1063  * Delete a script by name.
1064  *
1065  * Returns 0 if the script was deleted.
1066  *       1 if the script was not found.
1067  *       2 if the script cannot be deleted because it is active.
1068  */
1069 int msiv_deletescript(struct sdm_userdata *u, char *script_name) {
1070         struct sdm_script *s = NULL;
1071         struct sdm_script *script_to_delete = NULL;
1072
1073         for (s=u->first_script; s!=NULL; s=s->next) {
1074                 if (!strcasecmp(s->script_name, script_name)) {
1075                         script_to_delete = s;
1076                         if (s->script_active) {
1077                                 return(2);
1078                         }
1079                 }
1080         }
1081
1082         if (script_to_delete == NULL) return(1);
1083
1084         if (u->first_script == script_to_delete) {
1085                 u->first_script = u->first_script->next;
1086         }
1087         else for (s=u->first_script; s!=NULL; s=s->next) {
1088                 if (s->next == script_to_delete) {
1089                         s->next = s->next->next;
1090                 }
1091         }
1092
1093         free(script_to_delete->script_content);
1094         free(script_to_delete);
1095         return(0);
1096 }
1097
1098
1099 /*
1100  * Add or replace a new script.  
1101  * NOTE: after this function returns, "u" owns the memory that "script_content"
1102  * was pointing to.
1103  */
1104 void msiv_putscript(struct sdm_userdata *u, char *script_name, char *script_content) {
1105         int replaced = 0;
1106         struct sdm_script *s, *sptr;
1107
1108         for (s=u->first_script; s!=NULL; s=s->next) {
1109                 if (!strcasecmp(s->script_name, script_name)) {
1110                         if (s->script_content != NULL) {
1111                                 free(s->script_content);
1112                         }
1113                         s->script_content = script_content;
1114                         replaced = 1;
1115                 }
1116         }
1117
1118         if (replaced == 0) {
1119                 sptr = malloc(sizeof(struct sdm_script));
1120                 safestrncpy(sptr->script_name, script_name, sizeof sptr->script_name);
1121                 sptr->script_content = script_content;
1122                 sptr->script_active = 0;
1123                 sptr->next = u->first_script;
1124                 u->first_script = sptr;
1125         }
1126 }
1127
1128
1129
1130 /*
1131  * Citadel protocol to manage sieve scripts.
1132  * This is basically a simplified (read: doesn't resemble IMAP) version
1133  * of the 'managesieve' protocol.
1134  */
1135 void cmd_msiv(char *argbuf) {
1136         char subcmd[256];
1137         struct sdm_userdata u;
1138         char script_name[256];
1139         char *script_content = NULL;
1140         struct sdm_script *s;
1141         int i;
1142         int changes_made = 0;
1143
1144         memset(&u, 0, sizeof(struct sdm_userdata));
1145
1146         if (CtdlAccessCheck(ac_logged_in)) return;
1147         extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
1148         msiv_load(&u);
1149
1150         if (!strcasecmp(subcmd, "putscript")) {
1151                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
1152                 if (!IsEmptyStr(script_name)) {
1153                         cprintf("%d Transmit script now\n", SEND_LISTING);
1154                         script_content = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
1155                         msiv_putscript(&u, script_name, script_content);
1156                         changes_made = 1;
1157                 }
1158                 else {
1159                         cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
1160                 }
1161         }       
1162         
1163         else if (!strcasecmp(subcmd, "listscripts")) {
1164                 cprintf("%d Scripts:\n", LISTING_FOLLOWS);
1165                 for (s=u.first_script; s!=NULL; s=s->next) {
1166                         if (s->script_content != NULL) {
1167                                 cprintf("%s|%d|\n", s->script_name, s->script_active);
1168                         }
1169                 }
1170                 cprintf("000\n");
1171         }
1172
1173         else if (!strcasecmp(subcmd, "setactive")) {
1174                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
1175                 if (msiv_setactive(&u, script_name) == 0) {
1176                         cprintf("%d ok\n", CIT_OK);
1177                         changes_made = 1;
1178                 }
1179                 else {
1180                         cprintf("%d Script '%s' does not exist.\n",
1181                                 ERROR + ILLEGAL_VALUE,
1182                                 script_name
1183                         );
1184                 }
1185         }
1186
1187         else if (!strcasecmp(subcmd, "getscript")) {
1188                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
1189                 script_content = msiv_getscript(&u, script_name);
1190                 if (script_content != NULL) {
1191                         int script_len;
1192
1193                         cprintf("%d Script:\n", LISTING_FOLLOWS);
1194                         script_len = strlen(script_content);
1195                         client_write(script_content, script_len);
1196                         if (script_content[script_len-1] != '\n') {
1197                                 cprintf("\n");
1198                         }
1199                         cprintf("000\n");
1200                 }
1201                 else {
1202                         cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
1203                 }
1204         }
1205
1206         else if (!strcasecmp(subcmd, "deletescript")) {
1207                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
1208                 i = msiv_deletescript(&u, script_name);
1209                 if (i == 0) {
1210                         cprintf("%d ok\n", CIT_OK);
1211                         changes_made = 1;
1212                 }
1213                 else if (i == 1) {
1214                         cprintf("%d Script '%s' does not exist.\n",
1215                                 ERROR + ILLEGAL_VALUE,
1216                                 script_name
1217                         );
1218                 }
1219                 else if (i == 2) {
1220                         cprintf("%d Script '%s' is active and cannot be deleted.\n",
1221                                 ERROR + ILLEGAL_VALUE,
1222                                 script_name
1223                         );
1224                 }
1225                 else {
1226                         cprintf("%d unknown error\n", ERROR);
1227                 }
1228         }
1229
1230         else {
1231                 cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
1232         }
1233
1234         msiv_store(&u, changes_made);
1235 }
1236
1237
1238
1239 void ctdl_sieve_init(void) {
1240         char *cred = NULL;
1241         sieve2_context_t *sieve2_context = NULL;
1242         int res;
1243
1244         /*
1245          *      We don't really care about dumping the entire credits to the log
1246          *      every time the server is initialized.  The documentation will suffice
1247          *      for that purpose.  We are making a call to sieve2_credits() in order
1248          *      to demonstrate that we have successfully linked in to libsieve.
1249          */
1250         cred = strdup(sieve2_credits());
1251         if (cred == NULL) return;
1252
1253         if (strlen(cred) > 60) {
1254                 strcpy(&cred[55], "...");
1255         }
1256
1257         syslog(LOG_INFO, "%s\n",cred);
1258         free(cred);
1259
1260         /* Briefly initialize a Sieve parser instance just so we can list the
1261          * extensions that are available.
1262          */
1263         res = sieve2_alloc(&sieve2_context);
1264         if (res != SIEVE2_OK) {
1265                 syslog(LOG_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
1266                 return;
1267         }
1268
1269         res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
1270         if (res != SIEVE2_OK) {
1271                 syslog(LOG_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
1272                 goto BAIL;
1273         }
1274
1275         msiv_extensions = strdup(sieve2_listextensions(sieve2_context));
1276         syslog(LOG_INFO, "Extensions: %s\n", msiv_extensions);
1277
1278 BAIL:   res = sieve2_free(&sieve2_context);
1279         if (res != SIEVE2_OK) {
1280                 syslog(LOG_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
1281         }
1282
1283 }
1284
1285 void cleanup_sieve(void)
1286 {
1287         struct RoomProcList *ptr, *ptr2;
1288
1289         if (msiv_extensions != NULL)
1290                 free(msiv_extensions);
1291         msiv_extensions = NULL;
1292
1293         begin_critical_section(S_SIEVELIST);
1294         ptr=sieve_list;
1295         while (ptr != NULL) {
1296                 ptr2 = ptr->next;
1297                 free(ptr);
1298                 ptr = ptr2;
1299         }
1300         sieve_list = NULL;
1301         end_critical_section(S_SIEVELIST);
1302 }
1303
1304 int serv_sieve_room(struct ctdlroom *room)
1305 {
1306         if (!strcasecmp(&room->QRname[11], MAILROOM)) {
1307                 sieve_queue_room(room);
1308         }
1309         return 0;
1310 }
1311
1312 CTDL_MODULE_INIT(sieve)
1313 {
1314         if (!threading)
1315         {
1316
1317                 ctdl_sieve_init();
1318                 CtdlRegisterProtoHook(cmd_msiv, "MSIV", "Manage Sieve scripts");
1319                 CtdlRegisterRoomHook(serv_sieve_room);
1320                 CtdlRegisterSessionHook(perform_sieve_processing, EVT_HOUSE);
1321                 CtdlRegisterCleanupHook(cleanup_sieve);
1322         }
1323         
1324         /* return our module name for the log */
1325         return "sieve";
1326 }
1327