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