Removed the logging facility from citserver, use syslog instead
[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         sieve2_setvalue_string(sieve2_context, "allheaders", my.rfc822headers);
640         
641         syslog(LOG_DEBUG, "Calling sieve2_execute()\n");
642         res = sieve2_execute(sieve2_context, &my);
643         if (res != SIEVE2_OK) {
644                 syslog(LOG_CRIT, "sieve2_execute() returned %d: %s\n", res, sieve2_errstr(res));
645         }
646
647         free(my.rfc822headers);
648         my.rfc822headers = NULL;
649
650         /*
651          * Delete the message from the inbox unless either we were told not to, or
652          * if no other action was successfully taken.
653          */
654         if ( (!my.keep) && (my.cancel_implicit_keep) ) {
655                 syslog(LOG_DEBUG, "keep is 0 -- deleting message from inbox\n");
656                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
657         }
658
659         syslog(LOG_DEBUG, "Completed sieve processing on msg <%ld>\n", msgnum);
660         u->lastproc = msgnum;
661
662         return;
663 }
664
665
666
667 /*
668  * Given the on-disk representation of our Sieve config, load
669  * it into an in-memory data structure.
670  */
671 void parse_sieve_config(char *conf, struct sdm_userdata *u) {
672         char *ptr;
673         char *c, *vacrec;
674         char keyword[256];
675         struct sdm_script *sptr;
676         struct sdm_vacation *vptr;
677
678         ptr = conf;
679         while (c = ptr, ptr = bmstrcasestr(ptr, CTDLSIEVECONFIGSEPARATOR), ptr != NULL) {
680                 *ptr = 0;
681                 ptr += strlen(CTDLSIEVECONFIGSEPARATOR);
682
683                 extract_token(keyword, c, 0, '|', sizeof keyword);
684
685                 if (!strcasecmp(keyword, "lastproc")) {
686                         u->lastproc = extract_long(c, 1);
687                 }
688
689                 else if (!strcasecmp(keyword, "script")) {
690                         sptr = malloc(sizeof(struct sdm_script));
691                         extract_token(sptr->script_name, c, 1, '|', sizeof sptr->script_name);
692                         sptr->script_active = extract_int(c, 2);
693                         remove_token(c, 0, '|');
694                         remove_token(c, 0, '|');
695                         remove_token(c, 0, '|');
696                         sptr->script_content = strdup(c);
697                         sptr->next = u->first_script;
698                         u->first_script = sptr;
699                 }
700
701                 else if (!strcasecmp(keyword, "vacation")) {
702
703                         if (c != NULL) while (vacrec=c, c=strchr(c, '\n'), (c != NULL)) {
704
705                                 *c = 0;
706                                 ++c;
707
708                                 if (strncasecmp(vacrec, "vacation|", 9)) {
709                                         vptr = malloc(sizeof(struct sdm_vacation));
710                                         extract_token(vptr->fromaddr, vacrec, 0, '|', sizeof vptr->fromaddr);
711                                         vptr->timestamp = extract_long(vacrec, 1);
712                                         vptr->next = u->first_vacation;
713                                         u->first_vacation = vptr;
714                                 }
715                         }
716                 }
717
718                 /* ignore unknown keywords */
719         }
720 }
721
722 /*
723  * We found the Sieve configuration for this user.
724  * Now do something with it.
725  */
726 void get_sieve_config_backend(long msgnum, void *userdata) {
727         struct sdm_userdata *u = (struct sdm_userdata *) userdata;
728         struct CtdlMessage *msg;
729         char *conf;
730
731         u->config_msgnum = msgnum;
732         msg = CtdlFetchMessage(msgnum, 1);
733         if (msg == NULL) {
734                 u->config_msgnum = (-1) ;
735                 return;
736         }
737
738         conf = msg->cm_fields['M'];
739         msg->cm_fields['M'] = NULL;
740         CtdlFreeMessage(msg);
741
742         if (conf != NULL) {
743                 parse_sieve_config(conf, u);
744                 free(conf);
745         }
746
747 }
748
749
750 /* 
751  * Write our citadel sieve config back to disk
752  * 
753  * (Set yes_write_to_disk to nonzero to make it actually write the config;
754  * otherwise it just frees the data structures.)
755  */
756 void rewrite_ctdl_sieve_config(struct sdm_userdata *u, int yes_write_to_disk) {
757         StrBuf *text;
758         struct sdm_script *sptr;
759         struct sdm_vacation *vptr;
760         
761         text = NewStrBufPlain(NULL, SIZ);
762         StrBufPrintf(text,
763                      "Content-type: application/x-citadel-sieve-config\n"
764                      "\n"
765                      CTDLSIEVECONFIGSEPARATOR
766                      "lastproc|%ld"
767                      CTDLSIEVECONFIGSEPARATOR
768                      ,
769                      u->lastproc
770                 );
771
772         while (u->first_script != NULL) {
773                 StrBufAppendPrintf(text,
774                                    "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR,
775                                    u->first_script->script_name,
776                                    u->first_script->script_active,
777                                    u->first_script->script_content
778                         );
779                 sptr = u->first_script;
780                 u->first_script = u->first_script->next;
781                 free(sptr->script_content);
782                 free(sptr);
783         }
784
785         if (u->first_vacation != NULL) {
786
787                 StrBufAppendPrintf(text, "vacation|\n");
788                 while (u->first_vacation != NULL) {
789                         if ( (time(NULL) - u->first_vacation->timestamp) < (MAX_VACATION * 86400)) {
790                                 StrBufAppendPrintf(text, "%s|%ld\n",
791                                                    u->first_vacation->fromaddr,
792                                                    u->first_vacation->timestamp
793                                         );
794                         }
795                         vptr = u->first_vacation;
796                         u->first_vacation = u->first_vacation->next;
797                         free(vptr);
798                 }
799                 StrBufAppendPrintf(text, CTDLSIEVECONFIGSEPARATOR);
800         }
801
802         if (yes_write_to_disk)
803         {
804                 /* Save the config */
805                 quickie_message("Citadel", NULL, NULL, u->config_roomname,
806                                 ChrPtr(text),
807                                 4,
808                                 "Sieve configuration"
809                 );
810                 
811                 /* And delete the old one */
812                 if (u->config_msgnum > 0) {
813                         CtdlDeleteMessages(u->config_roomname, &u->config_msgnum, 1, "");
814                 }
815         }
816
817         FreeStrBuf (&text);
818
819 }
820
821
822 /*
823  * This is our callback registration table for libSieve.
824  */
825 sieve2_callback_t ctdl_sieve_callbacks[] = {
826         { SIEVE2_ACTION_REJECT,         ctdl_reject             },
827         { SIEVE2_ACTION_VACATION,       ctdl_vacation           },
828         { SIEVE2_ERRCALL_PARSE,         ctdl_errparse           },
829         { SIEVE2_ERRCALL_RUNTIME,       ctdl_errexec            },
830         { SIEVE2_ACTION_FILEINTO,       ctdl_fileinto           },
831         { SIEVE2_ACTION_REDIRECT,       ctdl_redirect           },
832         { SIEVE2_ACTION_DISCARD,        ctdl_discard            },
833         { SIEVE2_ACTION_KEEP,           ctdl_keep               },
834         { SIEVE2_SCRIPT_GETSCRIPT,      ctdl_getscript          },
835         { SIEVE2_DEBUG_TRACE,           ctdl_debug              },
836         { SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders         },
837         { SIEVE2_MESSAGE_GETSIZE,       ctdl_getsize            },
838         { SIEVE2_MESSAGE_GETENVELOPE,   ctdl_getenvelope        },
839 /*
840  * These actions are unsupported by Citadel so we don't declare them.
841  *
842         { SIEVE2_ACTION_NOTIFY,         ctdl_notify             },
843         { SIEVE2_MESSAGE_GETSUBADDRESS, ctdl_getsubaddress      },
844         { SIEVE2_MESSAGE_GETBODY,       ctdl_getbody            },
845  *
846  */
847         { 0 }
848 };
849
850
851 /*
852  * Perform sieve processing for a single room
853  */
854 void sieve_do_room(char *roomname) {
855         
856         struct sdm_userdata u;
857         sieve2_context_t *sieve2_context = NULL;        /* Context for sieve parser */
858         int res;                                        /* Return code from libsieve calls */
859         long orig_lastproc = 0;
860
861         memset(&u, 0, sizeof u);
862
863         /* See if the user who owns this 'mailbox' has any Sieve scripts that
864          * require execution.
865          */
866         snprintf(u.config_roomname, sizeof u.config_roomname, "%010ld.%s", atol(roomname), USERCONFIGROOM);
867         if (CtdlGetRoom(&CC->room, u.config_roomname) != 0) {
868                 syslog(LOG_DEBUG, "<%s> does not exist.  No processing is required.\n", u.config_roomname);
869                 return;
870         }
871
872         /*
873          * Find the sieve scripts and control record and do something
874          */
875         u.config_msgnum = (-1);
876         CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
877                 get_sieve_config_backend, (void *)&u );
878
879         if (u.config_msgnum < 0) {
880                 syslog(LOG_DEBUG, "No Sieve rules exist.  No processing is required.\n");
881                 return;
882         }
883
884         syslog(LOG_DEBUG, "Rules found.  Performing Sieve processing for <%s>\n", roomname);
885
886         if (CtdlGetRoom(&CC->room, roomname) != 0) {
887                 syslog(LOG_CRIT, "ERROR: cannot load <%s>\n", roomname);
888                 return;
889         }
890
891         /* Initialize the Sieve parser */
892         
893         res = sieve2_alloc(&sieve2_context);
894         if (res != SIEVE2_OK) {
895                 syslog(LOG_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
896                 return;
897         }
898
899         res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
900         if (res != SIEVE2_OK) {
901                 syslog(LOG_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
902                 goto BAIL;
903         }
904
905         /* Validate the script */
906
907         struct ctdl_sieve my;           /* dummy ctdl_sieve struct just to pass "u" slong */
908         memset(&my, 0, sizeof my);
909         my.u = &u;
910         res = sieve2_validate(sieve2_context, &my);
911         if (res != SIEVE2_OK) {
912                 syslog(LOG_CRIT, "sieve2_validate() returned %d: %s\n", res, sieve2_errstr(res));
913                 goto BAIL;
914         }
915
916         /* Do something useful */
917         u.sieve2_context = sieve2_context;
918         orig_lastproc = u.lastproc;
919         CtdlForEachMessage(MSGS_GT, u.lastproc, NULL, NULL, NULL,
920                 sieve_do_msg,
921                 (void *) &u
922         );
923
924 BAIL:
925         res = sieve2_free(&sieve2_context);
926         if (res != SIEVE2_OK) {
927                 syslog(LOG_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
928         }
929
930         /* Rewrite the config if we have to */
931         rewrite_ctdl_sieve_config(&u, (u.lastproc > orig_lastproc) ) ;
932 }
933
934
935 /*
936  * Perform sieve processing for all rooms which require it
937  */
938 void perform_sieve_processing(void) {
939         struct RoomProcList *ptr = NULL;
940
941         if (sieve_list != NULL) {
942                 syslog(LOG_DEBUG, "Begin Sieve processing\n");
943                 while (sieve_list != NULL) {
944                         char spoolroomname[ROOMNAMELEN];
945                         safestrncpy(spoolroomname, sieve_list->name, sizeof spoolroomname);
946                         begin_critical_section(S_SIEVELIST);
947
948                         /* pop this record off the list */
949                         ptr = sieve_list;
950                         sieve_list = sieve_list->next;
951                         free(ptr);
952
953                         /* invalidate any duplicate entries to prevent double processing */
954                         for (ptr=sieve_list; ptr!=NULL; ptr=ptr->next) {
955                                 if (!strcasecmp(ptr->name, spoolroomname)) {
956                                         ptr->name[0] = 0;
957                                 }
958                         }
959
960                         end_critical_section(S_SIEVELIST);
961                         if (spoolroomname[0] != 0) {
962                                 sieve_do_room(spoolroomname);
963                         }
964                 }
965         }
966 }
967
968
969 void msiv_load(struct sdm_userdata *u) {
970         char hold_rm[ROOMNAMELEN];
971
972         strcpy(hold_rm, CC->room.QRname);       /* save current room */
973
974         /* Take a spin through the user's personal address book */
975         if (CtdlGetRoom(&CC->room, USERCONFIGROOM) == 0) {
976         
977                 u->config_msgnum = (-1);
978                 strcpy(u->config_roomname, CC->room.QRname);
979                 CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
980                         get_sieve_config_backend, (void *)u );
981
982         }
983
984         if (strcmp(CC->room.QRname, hold_rm)) {
985                 CtdlGetRoom(&CC->room, hold_rm);    /* return to saved room */
986         }
987 }
988
989 void msiv_store(struct sdm_userdata *u, int yes_write_to_disk) {
990 /*
991  * Initialise the sieve configs last processed message number.
992  * We don't need to get the highest message number for the users inbox since the systems
993  * highest message number will be higher than that and loer than this scripts message number
994  * This prevents this new script from processing any old messages in the inbox.
995  * Most importantly it will prevent vacation messages being sent to lots of old messages
996  * in the inbox.
997  */
998         u->lastproc = CtdlGetCurrentMessageNumber();
999         rewrite_ctdl_sieve_config(u, yes_write_to_disk);
1000 }
1001
1002
1003 /*
1004  * Select the active script.
1005  * (Set script_name to an empty string to disable all scripts)
1006  * 
1007  * Returns 0 on success or nonzero for error.
1008  */
1009 int msiv_setactive(struct sdm_userdata *u, char *script_name) {
1010         int ok = 0;
1011         struct sdm_script *s;
1012
1013         /* First see if the supplied value is ok */
1014
1015         if (IsEmptyStr(script_name)) {
1016                 ok = 1;
1017         }
1018         else {
1019                 for (s=u->first_script; s!=NULL; s=s->next) {
1020                         if (!strcasecmp(s->script_name, script_name)) {
1021                                 ok = 1;
1022                         }
1023                 }
1024         }
1025
1026         if (!ok) return(-1);
1027
1028         /* Now set the active script */
1029         for (s=u->first_script; s!=NULL; s=s->next) {
1030                 if (!strcasecmp(s->script_name, script_name)) {
1031                         s->script_active = 1;
1032                 }
1033                 else {
1034                         s->script_active = 0;
1035                 }
1036         }
1037         
1038         return(0);
1039 }
1040
1041
1042 /*
1043  * Fetch a script by name.
1044  *
1045  * Returns NULL if the named script was not found, or a pointer to the script
1046  * if it was found.   NOTE: the caller does *not* own the memory returned by
1047  * this function.  Copy it if you need to keep it.
1048  */
1049 char *msiv_getscript(struct sdm_userdata *u, char *script_name) {
1050         struct sdm_script *s;
1051
1052         for (s=u->first_script; s!=NULL; s=s->next) {
1053                 if (!strcasecmp(s->script_name, script_name)) {
1054                         if (s->script_content != NULL) {
1055                                 return (s->script_content);
1056                         }
1057                 }
1058         }
1059
1060         return(NULL);
1061 }
1062
1063
1064 /*
1065  * Delete a script by name.
1066  *
1067  * Returns 0 if the script was deleted.
1068  *       1 if the script was not found.
1069  *       2 if the script cannot be deleted because it is active.
1070  */
1071 int msiv_deletescript(struct sdm_userdata *u, char *script_name) {
1072         struct sdm_script *s = NULL;
1073         struct sdm_script *script_to_delete = NULL;
1074
1075         for (s=u->first_script; s!=NULL; s=s->next) {
1076                 if (!strcasecmp(s->script_name, script_name)) {
1077                         script_to_delete = s;
1078                         if (s->script_active) {
1079                                 return(2);
1080                         }
1081                 }
1082         }
1083
1084         if (script_to_delete == NULL) return(1);
1085
1086         if (u->first_script == script_to_delete) {
1087                 u->first_script = u->first_script->next;
1088         }
1089         else for (s=u->first_script; s!=NULL; s=s->next) {
1090                 if (s->next == script_to_delete) {
1091                         s->next = s->next->next;
1092                 }
1093         }
1094
1095         free(script_to_delete->script_content);
1096         free(script_to_delete);
1097         return(0);
1098 }
1099
1100
1101 /*
1102  * Add or replace a new script.  
1103  * NOTE: after this function returns, "u" owns the memory that "script_content"
1104  * was pointing to.
1105  */
1106 void msiv_putscript(struct sdm_userdata *u, char *script_name, char *script_content) {
1107         int replaced = 0;
1108         struct sdm_script *s, *sptr;
1109
1110         for (s=u->first_script; s!=NULL; s=s->next) {
1111                 if (!strcasecmp(s->script_name, script_name)) {
1112                         if (s->script_content != NULL) {
1113                                 free(s->script_content);
1114                         }
1115                         s->script_content = script_content;
1116                         replaced = 1;
1117                 }
1118         }
1119
1120         if (replaced == 0) {
1121                 sptr = malloc(sizeof(struct sdm_script));
1122                 safestrncpy(sptr->script_name, script_name, sizeof sptr->script_name);
1123                 sptr->script_content = script_content;
1124                 sptr->script_active = 0;
1125                 sptr->next = u->first_script;
1126                 u->first_script = sptr;
1127         }
1128 }
1129
1130
1131
1132 /*
1133  * Citadel protocol to manage sieve scripts.
1134  * This is basically a simplified (read: doesn't resemble IMAP) version
1135  * of the 'managesieve' protocol.
1136  */
1137 void cmd_msiv(char *argbuf) {
1138         char subcmd[256];
1139         struct sdm_userdata u;
1140         char script_name[256];
1141         char *script_content = NULL;
1142         struct sdm_script *s;
1143         int i;
1144         int changes_made = 0;
1145
1146         memset(&u, 0, sizeof(struct sdm_userdata));
1147
1148         if (CtdlAccessCheck(ac_logged_in)) return;
1149         extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
1150         msiv_load(&u);
1151
1152         if (!strcasecmp(subcmd, "putscript")) {
1153                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
1154                 if (!IsEmptyStr(script_name)) {
1155                         cprintf("%d Transmit script now\n", SEND_LISTING);
1156                         script_content = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
1157                         msiv_putscript(&u, script_name, script_content);
1158                         changes_made = 1;
1159                 }
1160                 else {
1161                         cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
1162                 }
1163         }       
1164         
1165         else if (!strcasecmp(subcmd, "listscripts")) {
1166                 cprintf("%d Scripts:\n", LISTING_FOLLOWS);
1167                 for (s=u.first_script; s!=NULL; s=s->next) {
1168                         if (s->script_content != NULL) {
1169                                 cprintf("%s|%d|\n", s->script_name, s->script_active);
1170                         }
1171                 }
1172                 cprintf("000\n");
1173         }
1174
1175         else if (!strcasecmp(subcmd, "setactive")) {
1176                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
1177                 if (msiv_setactive(&u, script_name) == 0) {
1178                         cprintf("%d ok\n", CIT_OK);
1179                         changes_made = 1;
1180                 }
1181                 else {
1182                         cprintf("%d Script '%s' does not exist.\n",
1183                                 ERROR + ILLEGAL_VALUE,
1184                                 script_name
1185                         );
1186                 }
1187         }
1188
1189         else if (!strcasecmp(subcmd, "getscript")) {
1190                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
1191                 script_content = msiv_getscript(&u, script_name);
1192                 if (script_content != NULL) {
1193                         int script_len;
1194
1195                         cprintf("%d Script:\n", LISTING_FOLLOWS);
1196                         script_len = strlen(script_content);
1197                         client_write(script_content, script_len);
1198                         if (script_content[script_len-1] != '\n') {
1199                                 cprintf("\n");
1200                         }
1201                         cprintf("000\n");
1202                 }
1203                 else {
1204                         cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
1205                 }
1206         }
1207
1208         else if (!strcasecmp(subcmd, "deletescript")) {
1209                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
1210                 i = msiv_deletescript(&u, script_name);
1211                 if (i == 0) {
1212                         cprintf("%d ok\n", CIT_OK);
1213                         changes_made = 1;
1214                 }
1215                 else if (i == 1) {
1216                         cprintf("%d Script '%s' does not exist.\n",
1217                                 ERROR + ILLEGAL_VALUE,
1218                                 script_name
1219                         );
1220                 }
1221                 else if (i == 2) {
1222                         cprintf("%d Script '%s' is active and cannot be deleted.\n",
1223                                 ERROR + ILLEGAL_VALUE,
1224                                 script_name
1225                         );
1226                 }
1227                 else {
1228                         cprintf("%d unknown error\n", ERROR);
1229                 }
1230         }
1231
1232         else {
1233                 cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
1234         }
1235
1236         msiv_store(&u, changes_made);
1237 }
1238
1239
1240
1241 void ctdl_sieve_init(void) {
1242         char *cred = NULL;
1243         sieve2_context_t *sieve2_context = NULL;
1244         int res;
1245
1246         /*
1247          *      We don't really care about dumping the entire credits to the log
1248          *      every time the server is initialized.  The documentation will suffice
1249          *      for that purpose.  We are making a call to sieve2_credits() in order
1250          *      to demonstrate that we have successfully linked in to libsieve.
1251          */
1252         cred = strdup(sieve2_credits());
1253         if (cred == NULL) return;
1254
1255         if (strlen(cred) > 60) {
1256                 strcpy(&cred[55], "...");
1257         }
1258
1259         syslog(LOG_INFO, "%s\n",cred);
1260         free(cred);
1261
1262         /* Briefly initialize a Sieve parser instance just so we can list the
1263          * extensions that are available.
1264          */
1265         res = sieve2_alloc(&sieve2_context);
1266         if (res != SIEVE2_OK) {
1267                 syslog(LOG_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
1268                 return;
1269         }
1270
1271         res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
1272         if (res != SIEVE2_OK) {
1273                 syslog(LOG_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
1274                 goto BAIL;
1275         }
1276
1277         msiv_extensions = strdup(sieve2_listextensions(sieve2_context));
1278         syslog(LOG_INFO, "Extensions: %s\n", msiv_extensions);
1279
1280 BAIL:   res = sieve2_free(&sieve2_context);
1281         if (res != SIEVE2_OK) {
1282                 syslog(LOG_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
1283         }
1284
1285 }
1286
1287 void cleanup_sieve(void)
1288 {
1289         struct RoomProcList *ptr, *ptr2;
1290
1291         if (msiv_extensions != NULL)
1292                 free(msiv_extensions);
1293         msiv_extensions = NULL;
1294
1295         begin_critical_section(S_SIEVELIST);
1296         ptr=sieve_list;
1297         while (ptr != NULL) {
1298                 ptr2 = ptr->next;
1299                 free(ptr);
1300                 ptr = ptr2;
1301         }
1302         sieve_list = NULL;
1303         end_critical_section(S_SIEVELIST);
1304 }
1305
1306 int serv_sieve_room(struct ctdlroom *room)
1307 {
1308         if (!strcasecmp(&room->QRname[11], MAILROOM)) {
1309                 sieve_queue_room(room);
1310         }
1311         return 0;
1312 }
1313
1314 CTDL_MODULE_INIT(sieve)
1315 {
1316         if (!threading)
1317         {
1318
1319                 ctdl_sieve_init();
1320                 CtdlRegisterProtoHook(cmd_msiv, "MSIV", "Manage Sieve scripts");
1321                 CtdlRegisterRoomHook(serv_sieve_room);
1322                 CtdlRegisterSessionHook(perform_sieve_processing, EVT_HOUSE);
1323                 CtdlRegisterCleanupHook(cleanup_sieve);
1324         }
1325         
1326         /* return our Subversion id for the Log */
1327         return "sieve";
1328 }
1329