Put in a stub for msiv_listextensions() to remind myself
[citadel.git] / citadel / serv_sieve.c
1 /*
2  * $Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $
3  *
4  * This module glues libSieve to the Citadel server in order to implement
5  * the Sieve mailbox filtering language (RFC 3028).
6  *
7  * This code is released under the terms of the GNU General Public License. 
8  */
9
10 #include "sysdep.h"
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <stdio.h>
14 #include <fcntl.h>
15 #include <signal.h>
16 #include <pwd.h>
17 #include <errno.h>
18 #include <sys/types.h>
19
20 #if TIME_WITH_SYS_TIME
21 # include <sys/time.h>
22 # include <time.h>
23 #else
24 # if HAVE_SYS_TIME_H
25 #  include <sys/time.h>
26 # else
27 #  include <time.h>
28 # endif
29 #endif
30
31 #include <sys/wait.h>
32 #include <string.h>
33 #include <limits.h>
34 #include "citadel.h"
35 #include "server.h"
36 #include "sysdep_decls.h"
37 #include "citserver.h"
38 #include "support.h"
39 #include "config.h"
40 #include "serv_extensions.h"
41 #include "room_ops.h"
42 #include "policy.h"
43 #include "database.h"
44 #include "msgbase.h"
45 #include "internet_addressing.h"
46 #include "tools.h"
47
48 #ifdef HAVE_LIBSIEVE
49
50 #include "serv_sieve.h"
51
52 struct RoomProcList *sieve_list = NULL;
53
54
55 /*
56  * Callback function to send libSieve trace messages to Citadel log facility
57  * Set ctdl_libsieve_debug=1 to see extremely verbose libSieve trace
58  */
59 int ctdl_debug(sieve2_context_t *s, void *my)
60 {
61         static int ctdl_libsieve_debug = 0;
62
63         if (ctdl_libsieve_debug) {
64                 lprintf(CTDL_DEBUG, "Sieve: level [%d] module [%s] file [%s] function [%s]\n",
65                         sieve2_getvalue_int(s, "level"),
66                         sieve2_getvalue_string(s, "module"),
67                         sieve2_getvalue_string(s, "file"),
68                         sieve2_getvalue_string(s, "function"));
69                 lprintf(CTDL_DEBUG, "       message [%s]\n",
70                         sieve2_getvalue_string(s, "message"));
71         }
72         return SIEVE2_OK;
73 }
74
75
76 /*
77  * Callback function to log script parsing errors
78  */
79 int ctdl_errparse(sieve2_context_t *s, void *my)
80 {
81         lprintf(CTDL_WARNING, "Error in script, line %d: %s\n",
82                 sieve2_getvalue_int(s, "lineno"),
83                 sieve2_getvalue_string(s, "message")
84         );
85         return SIEVE2_OK;
86 }
87
88
89 /*
90  * Callback function to log script execution errors
91  */
92 int ctdl_errexec(sieve2_context_t *s, void *my)
93 {
94         lprintf(CTDL_WARNING, "Error executing script: %s\n",
95                 sieve2_getvalue_string(s, "message")
96         );
97         return SIEVE2_OK;
98 }
99
100
101 /*
102  * Callback function to redirect a message to a different folder
103  */
104 int ctdl_redirect(sieve2_context_t *s, void *my)
105 {
106         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
107         struct CtdlMessage *msg = NULL;
108         struct recptypes *valid = NULL;
109         char recp[256];
110
111         safestrncpy(recp, sieve2_getvalue_string(s, "address"), sizeof recp);
112
113         lprintf(CTDL_DEBUG, "Action is REDIRECT, recipient <%s>\n", recp);
114
115         valid = validate_recipients(recp);
116         if (valid == NULL) {
117                 lprintf(CTDL_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
118                 return SIEVE2_ERROR_BADARGS;
119         }
120         if (valid->num_error > 0) {
121                 lprintf(CTDL_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
122                 free(valid);
123                 return SIEVE2_ERROR_BADARGS;
124         }
125
126         msg = CtdlFetchMessage(cs->msgnum, 1);
127         if (msg == NULL) {
128                 lprintf(CTDL_WARNING, "REDIRECT failed: unable to fetch msg %ld\n", cs->msgnum);
129                 free(valid);
130                 return SIEVE2_ERROR_BADARGS;
131         }
132
133         CtdlSubmitMsg(msg, valid, NULL);
134         cs->actiontaken = 1;
135         free(valid);
136         CtdlFreeMessage(msg);
137         return SIEVE2_OK;
138 }
139
140
141 /*
142  * Callback function to indicate that a message *will* be kept in the inbox
143  */
144 int ctdl_keep(sieve2_context_t *s, void *my)
145 {
146         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
147         
148         lprintf(CTDL_DEBUG, "Action is KEEP\n");
149
150         cs->keep = 1;
151         cs->actiontaken = 1;
152         return SIEVE2_OK;
153 }
154
155
156 /*
157  * Callback function to file a message into a different mailbox
158  */
159 int ctdl_fileinto(sieve2_context_t *s, void *my)
160 {
161         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
162         const char *dest_folder = sieve2_getvalue_string(s, "mailbox");
163         int c;
164         char foldername[256];
165         char original_room_name[ROOMNAMELEN];
166
167         lprintf(CTDL_DEBUG, "Action is FILEINTO, destination is <%s>\n", dest_folder);
168
169         /* FILEINTO 'INBOX' is the same thing as KEEP */
170         if ( (!strcasecmp(dest_folder, "INBOX")) || (!strcasecmp(dest_folder, MAILROOM)) ) {
171                 cs->keep = 1;
172                 cs->actiontaken = 1;
173                 return SIEVE2_OK;
174         }
175
176         /* Remember what room we came from */
177         safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
178
179         /* First try a mailbox name match (check personal mail folders first) */
180         snprintf(foldername, sizeof foldername, "%010ld.%s", cs->usernum, dest_folder);
181         c = getroom(&CC->room, foldername);
182
183         /* Then a regular room name match (public and private rooms) */
184         if (c < 0) {
185                 safestrncpy(foldername, dest_folder, sizeof foldername);
186                 c = getroom(&CC->room, foldername);
187         }
188
189         if (c < 0) {
190                 lprintf(CTDL_WARNING, "FILEINTO failed: target <%s> does not exist\n", dest_folder);
191                 return SIEVE2_ERROR_BADARGS;
192         }
193
194         /* Yes, we actually have to go there */
195         usergoto(NULL, 0, 0, NULL, NULL);
196
197         c = CtdlSaveMsgPointersInRoom(NULL, &cs->msgnum, 1, 0, NULL);
198
199         /* Go back to the room we came from */
200         if (strcasecmp(original_room_name, CC->room.QRname)) {
201                 usergoto(original_room_name, 0, 0, NULL, NULL);
202         }
203
204         if (c == 0) {
205                 cs->actiontaken = 1;
206                 return SIEVE2_OK;
207         }
208         else {
209                 return SIEVE2_ERROR_BADARGS;
210         }
211 }
212
213
214 /*
215  * Callback function to indicate that a message should be discarded.
216  */
217 int ctdl_discard(sieve2_context_t *s, void *my)
218 {
219         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
220
221         lprintf(CTDL_DEBUG, "Action is DISCARD\n");
222
223         /* Yes, this is really all there is to it.  Since we are not setting "keep" to 1,
224          * the message will be discarded because "some other action" was successfully taken.
225          */
226         cs->actiontaken = 1;
227         return SIEVE2_OK;
228 }
229
230
231
232 /*
233  * Callback function to indicate that a message should be rejected
234  */
235 int ctdl_reject(sieve2_context_t *s, void *my)
236 {
237         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
238         char *reject_text = NULL;
239
240         lprintf(CTDL_DEBUG, "Action is REJECT\n");
241
242         /* If we don't know who sent the message, do a DISCARD instead. */
243         if (strlen(cs->sender) == 0) {
244                 lprintf(CTDL_INFO, "Unknown sender.  Doing DISCARD instead of REJECT.\n");
245                 return ctdl_discard(s, my);
246         }
247
248         /* Assemble the reject message. */
249         reject_text = malloc(strlen(sieve2_getvalue_string(s, "message")) + 1024);
250         if (reject_text == NULL) {
251                 return SIEVE2_ERROR_FAIL;
252         }
253
254         sprintf(reject_text, 
255                 "Content-type: text/plain\n"
256                 "\n"
257                 "The message was refused by the recipient's mail filtering program.\n"
258                 "The reason given was as follows:\n"
259                 "\n"
260                 "%s\n"
261                 "\n"
262         ,
263                 sieve2_getvalue_string(s, "message")
264         );
265
266         quickie_message(        /* This delivers the message */
267                 "Citadel",
268                 cs->sender,
269                 NULL,
270                 reject_text,
271                 FMT_RFC822,
272                 "Delivery status notification"
273         );
274
275         free(reject_text);
276         cs->actiontaken = 1;
277         return SIEVE2_OK;
278 }
279
280
281 /*
282  * Callback function to indicate that the user should be notified
283  * FIXME implement this
284  */
285 int ctdl_notify(sieve2_context_t *s, void *my)
286 {
287         lprintf(CTDL_DEBUG, "Action is NOTIFY\n");
288         return SIEVE2_ERROR_UNSUPPORTED;
289 }
290
291
292 /*
293  * Callback function to indicate that a vacation message should be generated
294  * FIXME implement this
295  */
296 int ctdl_vacation(sieve2_context_t *s, void *my)
297 {
298         lprintf(CTDL_DEBUG, "Action is VACATION\n");
299         return SIEVE2_ERROR_UNSUPPORTED;
300 }
301
302
303 /*
304  * Callback function to parse addresses per local system convention
305  */
306 int ctdl_getsubaddress(sieve2_context_t *s, void *my)
307 {
308         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
309
310         /*
311          * Citadel doesn't support subaddresses, so just give up.
312          */
313         return SIEVE2_ERROR_UNSUPPORTED;
314
315         /* libSieve does not take ownership of the memory used here.  But, since we
316          * are just pointing to locations inside a struct which we are going to free
317          * later, we're ok.
318          */
319         sieve2_setvalue_string(s, "user", cs->recp_user);
320         sieve2_setvalue_string(s, "detail", "");        /* we don't support user+detail@domain yet */
321         sieve2_setvalue_string(s, "localpart", cs->recp_user);
322         sieve2_setvalue_string(s, "domain", cs->recp_node);
323         return SIEVE2_OK;
324 }
325
326
327 /*
328  * Callback function to parse message envelope
329  * FIXME implement this
330  */
331 int ctdl_getenvelope(sieve2_context_t *s, void *my)
332 {
333         return SIEVE2_ERROR_UNSUPPORTED;
334 }
335
336
337 /*
338  * Callback function to fetch message body
339  * FIXME implement this
340  */
341 int ctdl_getbody(sieve2_context_t *s, void *my)
342 {
343         return SIEVE2_ERROR_UNSUPPORTED;
344 }
345
346
347 /*
348  * Callback function to fetch message size
349  */
350 int ctdl_getsize(sieve2_context_t *s, void *my)
351 {
352         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
353         struct MetaData smi;
354
355         GetMetaData(&smi, cs->msgnum);
356         
357         if (smi.meta_rfc822_length > 0L) {
358                 sieve2_setvalue_int(s, "size", (int)smi.meta_rfc822_length);
359                 return SIEVE2_OK;
360         }
361
362         return SIEVE2_ERROR_UNSUPPORTED;
363 }
364
365
366 /*
367  * Callback function to retrieve the sieve script
368  */
369 int ctdl_getscript(sieve2_context_t *s, void *my) {
370         struct sdm_script *sptr;
371         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
372
373         for (sptr=cs->u->first_script; sptr!=NULL; sptr=sptr->next) {
374                 if (sptr->script_active > 0) {
375                         lprintf(CTDL_DEBUG, "ctdl_getscript() is using script '%s'\n", sptr->script_name);
376                         sieve2_setvalue_string(s, "script", sptr->script_content);
377                         return SIEVE2_OK;
378                 }
379         }
380                 
381         lprintf(CTDL_DEBUG, "ctdl_getscript() found no active script\n");
382         return SIEVE2_ERROR_GETSCRIPT;
383 }
384
385 /*
386  * Callback function to retrieve message headers
387  */
388 int ctdl_getheaders(sieve2_context_t *s, void *my) {
389
390         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
391
392         lprintf(CTDL_DEBUG, "ctdl_getheaders() was called\n");
393
394         sieve2_setvalue_string(s, "allheaders", cs->rfc822headers);
395         return SIEVE2_OK;
396 }
397
398
399
400 /*
401  * Add a room to the list of those rooms which potentially require sieve processing
402  */
403 void sieve_queue_room(struct ctdlroom *which_room) {
404         struct RoomProcList *ptr;
405
406         ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
407         if (ptr == NULL) return;
408
409         safestrncpy(ptr->name, which_room->QRname, sizeof ptr->name);
410         begin_critical_section(S_SIEVELIST);
411         ptr->next = sieve_list;
412         sieve_list = ptr;
413         end_critical_section(S_SIEVELIST);
414 }
415
416
417
418 /*
419  * Perform sieve processing for one message (called by sieve_do_room() for each message)
420  */
421 void sieve_do_msg(long msgnum, void *userdata) {
422         struct sdm_userdata *u = (struct sdm_userdata *) userdata;
423         sieve2_context_t *sieve2_context = u->sieve2_context;
424         struct ctdl_sieve my;
425         int res;
426         struct CtdlMessage *msg;
427
428         lprintf(CTDL_DEBUG, "Performing sieve processing on msg <%ld>\n", msgnum);
429
430         msg = CtdlFetchMessage(msgnum, 0);
431         if (msg == NULL) return;
432
433         CC->redirect_buffer = malloc(SIZ);
434         CC->redirect_len = 0;
435         CC->redirect_alloc = SIZ;
436         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
437         my.rfc822headers = CC->redirect_buffer;
438         CC->redirect_buffer = NULL;
439         CC->redirect_len = 0;
440         CC->redirect_alloc = 0;
441
442         my.keep = 0;            /* Don't keep a copy in the inbox unless a callback tells us to do so */
443         my.actiontaken = 0;     /* Keep track of whether any actions were successfully taken */
444         my.usernum = atol(CC->room.QRname);     /* Keep track of the owner of the room's namespace */
445         my.msgnum = msgnum;     /* Keep track of the message number in our local store */
446         my.u = u;               /* Hand off a pointer to the rest of this info */
447
448         /* Keep track of the recipient so we can do handling based on it later */
449         process_rfc822_addr(msg->cm_fields['R'], my.recp_user, my.recp_node, my.recp_name);
450
451         /* Keep track of the sender so we can use it for REJECT and VACATION responses */
452         if (msg->cm_fields['F'] != NULL) {
453                 safestrncpy(my.sender, msg->cm_fields['F'], sizeof my.sender);
454         }
455         else if ( (msg->cm_fields['A'] != NULL) && (msg->cm_fields['N'] != NULL) ) {
456                 snprintf(my.sender, sizeof my.sender, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
457         }
458         else if (msg->cm_fields['A'] != NULL) {
459                 safestrncpy(my.sender, msg->cm_fields['A'], sizeof my.sender);
460         }
461         else {
462                 strcpy(my.sender, "");
463         }
464
465         free(msg);
466
467         sieve2_setvalue_string(sieve2_context, "allheaders", my.rfc822headers);
468         
469         lprintf(CTDL_DEBUG, "Calling sieve2_execute()\n");
470         res = sieve2_execute(sieve2_context, &my);
471         if (res != SIEVE2_OK) {
472                 lprintf(CTDL_CRIT, "sieve2_execute() returned %d: %s\n", res, sieve2_errstr(res));
473         }
474
475         free(my.rfc822headers);
476         my.rfc822headers = NULL;
477
478         /*
479          * Delete the message from the inbox unless either we were told not to, or
480          * if no other action was successfully taken.
481          */
482         if ( (!my.keep) && (my.actiontaken) ) {
483                 lprintf(CTDL_DEBUG, "keep is 0 -- deleting message from inbox\n");
484                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "", 0);
485         }
486
487         lprintf(CTDL_DEBUG, "Completed sieve processing on msg <%ld>\n", msgnum);
488         u->lastproc = msgnum;
489
490         return;
491 }
492
493
494
495 /*
496  * Given the on-disk representation of our Sieve config, load
497  * it into an in-memory data structure.
498  */
499 void parse_sieve_config(char *conf, struct sdm_userdata *u) {
500         char *ptr;
501         char *c;
502         char keyword[256];
503         struct sdm_script *sptr;
504
505         ptr = conf;
506         while (c = ptr, ptr = bmstrcasestr(ptr, CTDLSIEVECONFIGSEPARATOR), ptr != NULL) {
507                 *ptr = 0;
508                 ptr += strlen(CTDLSIEVECONFIGSEPARATOR);
509
510                 extract_token(keyword, c, 0, '|', sizeof keyword);
511
512                 if (!strcasecmp(keyword, "lastproc")) {
513                         u->lastproc = extract_long(c, 1);
514                 }
515
516                 else if (!strcasecmp(keyword, "script")) {
517                         sptr = malloc(sizeof(struct sdm_script));
518                         extract_token(sptr->script_name, c, 1, '|', sizeof sptr->script_name);
519                         sptr->script_active = extract_int(c, 2);
520                         remove_token(c, 0, '|');
521                         remove_token(c, 0, '|');
522                         remove_token(c, 0, '|');
523                         sptr->script_content = strdup(c);
524                         sptr->next = u->first_script;
525                         u->first_script = sptr;
526                 }
527
528                 /* ignore unknown keywords */
529         }
530 }
531
532 /*
533  * We found the Sieve configuration for this user.
534  * Now do something with it.
535  */
536 void get_sieve_config_backend(long msgnum, void *userdata) {
537         struct sdm_userdata *u = (struct sdm_userdata *) userdata;
538         struct CtdlMessage *msg;
539         char *conf;
540
541         u->config_msgnum = msgnum;
542         msg = CtdlFetchMessage(msgnum, 1);
543         if (msg == NULL) {
544                 u->config_msgnum = (-1) ;
545                 return;
546         }
547
548         conf = msg->cm_fields['M'];
549         msg->cm_fields['M'] = NULL;
550         CtdlFreeMessage(msg);
551
552         if (conf != NULL) {
553                 parse_sieve_config(conf, u);
554                 free(conf);
555         }
556
557 }
558
559
560 /* 
561  * Write our citadel sieve config back to disk
562  */
563 void rewrite_ctdl_sieve_config(struct sdm_userdata *u) {
564         char *text;
565         struct sdm_script *sptr;
566
567
568         text = malloc(1024);
569         snprintf(text, 1024,
570                 "Content-type: application/x-citadel-sieve-config\n"
571                 "\n"
572                 CTDLSIEVECONFIGSEPARATOR
573                 "lastproc|%ld"
574                 CTDLSIEVECONFIGSEPARATOR
575         ,
576                 u->lastproc
577         );
578
579         while (u->first_script != NULL) {
580                 text = realloc(text, strlen(text) + strlen(u->first_script->script_content) + 256);
581                 sprintf(&text[strlen(text)], "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR,
582                         u->first_script->script_name,
583                         u->first_script->script_active,
584                         u->first_script->script_content
585                 );
586                 sptr = u->first_script;
587                 u->first_script = u->first_script->next;
588                 free(sptr->script_content);
589                 free(sptr);
590         }
591
592         /* Save the config */
593         quickie_message("Citadel", NULL, u->config_roomname,
594                         text,
595                         4,
596                         "Sieve configuration"
597         );
598
599         /* And delete the old one */
600         if (u->config_msgnum > 0) {
601                 CtdlDeleteMessages(u->config_roomname, &u->config_msgnum, 1, "", 0);
602         }
603
604 }
605
606
607 /*
608  * This is our callback registration table for libSieve.
609  */
610 sieve2_callback_t ctdl_sieve_callbacks[] = {
611         { SIEVE2_ACTION_REJECT,         ctdl_reject             },
612         { SIEVE2_ACTION_NOTIFY,         ctdl_notify             },
613         { SIEVE2_ACTION_VACATION,       ctdl_vacation           },
614         { SIEVE2_ERRCALL_PARSE,         ctdl_errparse           },
615         { SIEVE2_ERRCALL_RUNTIME,       ctdl_errexec            },
616         { SIEVE2_ACTION_FILEINTO,       ctdl_fileinto           },
617         { SIEVE2_ACTION_REDIRECT,       ctdl_redirect           },
618         { SIEVE2_ACTION_DISCARD,        ctdl_discard            },
619         { SIEVE2_ACTION_KEEP,           ctdl_keep               },
620         { SIEVE2_SCRIPT_GETSCRIPT,      ctdl_getscript          },
621         { SIEVE2_DEBUG_TRACE,           ctdl_debug              },
622         { SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders         },
623         { SIEVE2_MESSAGE_GETSUBADDRESS, ctdl_getsubaddress      },
624         { SIEVE2_MESSAGE_GETENVELOPE,   ctdl_getenvelope        },
625         { SIEVE2_MESSAGE_GETBODY,       ctdl_getbody            },
626         { SIEVE2_MESSAGE_GETSIZE,       ctdl_getsize            },
627         { 0 }
628 };
629
630
631 /*
632  * Perform sieve processing for a single room
633  */
634 void sieve_do_room(char *roomname) {
635         
636         struct sdm_userdata u;
637         sieve2_context_t *sieve2_context = NULL;        /* Context for sieve parser */
638         int res;                                        /* Return code from libsieve calls */
639         long orig_lastproc = 0;
640
641         memset(&u, 0, sizeof u);
642
643         /* See if the user who owns this 'mailbox' has any Sieve scripts that
644          * require execution.
645          */
646         snprintf(u.config_roomname, sizeof u.config_roomname, "%010ld.%s", atol(roomname), SIEVERULES);
647         if (getroom(&CC->room, u.config_roomname) != 0) {
648                 lprintf(CTDL_DEBUG, "<%s> does not exist.  No processing is required.\n", u.config_roomname);
649                 return;
650         }
651
652         /*
653          * Find the sieve scripts and control record and do something
654          */
655         u.config_msgnum = (-1);
656         CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
657                 get_sieve_config_backend, (void *)&u );
658
659         if (u.config_msgnum < 0) {
660                 lprintf(CTDL_DEBUG, "No Sieve rules exist.  No processing is required.\n");
661                 return;
662         }
663
664         lprintf(CTDL_DEBUG, "Rules found.  Performing Sieve processing for <%s>\n", roomname);
665
666         if (getroom(&CC->room, roomname) != 0) {
667                 lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", roomname);
668                 return;
669         }
670
671         /* Initialize the Sieve parser */
672         
673         res = sieve2_alloc(&sieve2_context);
674         if (res != SIEVE2_OK) {
675                 lprintf(CTDL_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
676                 return;
677         }
678
679         res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
680         if (res != SIEVE2_OK) {
681                 lprintf(CTDL_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
682                 goto BAIL;
683         }
684
685         /* Validate the script */
686
687         struct ctdl_sieve my;           /* dummy ctdl_sieve struct just to pass "u" slong */
688         memset(&my, 0, sizeof my);
689         my.u = &u;
690         res = sieve2_validate(sieve2_context, &my);
691         if (res != SIEVE2_OK) {
692                 lprintf(CTDL_CRIT, "sieve2_validate() returned %d: %s\n", res, sieve2_errstr(res));
693                 goto BAIL;
694         }
695
696         /* Do something useful */
697         u.sieve2_context = sieve2_context;
698         orig_lastproc = u.lastproc;
699         CtdlForEachMessage(MSGS_GT, u.lastproc, NULL, NULL, NULL,
700                 sieve_do_msg,
701                 (void *) &u
702         );
703
704 BAIL:
705         res = sieve2_free(&sieve2_context);
706         if (res != SIEVE2_OK) {
707                 lprintf(CTDL_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
708         }
709
710         /* Rewrite the config if we have to */
711         if (u.lastproc > orig_lastproc) {
712                 rewrite_ctdl_sieve_config(&u);
713         }
714 }
715
716
717 /*
718  * Perform sieve processing for all rooms which require it
719  */
720 void perform_sieve_processing(void) {
721         struct RoomProcList *ptr = NULL;
722
723         if (sieve_list != NULL) {
724                 lprintf(CTDL_DEBUG, "Begin Sieve processing\n");
725                 while (sieve_list != NULL) {
726                         char spoolroomname[ROOMNAMELEN];
727                         safestrncpy(spoolroomname, sieve_list->name, sizeof spoolroomname);
728                         begin_critical_section(S_SIEVELIST);
729
730                         /* pop this record off the list */
731                         ptr = sieve_list;
732                         sieve_list = sieve_list->next;
733                         free(ptr);
734
735                         /* invalidate any duplicate entries to prevent double processing */
736                         for (ptr=sieve_list; ptr!=NULL; ptr=ptr->next) {
737                                 if (!strcasecmp(ptr->name, spoolroomname)) {
738                                         ptr->name[0] = 0;
739                                 }
740                         }
741
742                         end_critical_section(S_SIEVELIST);
743                         if (spoolroomname[0] != 0) {
744                                 sieve_do_room(spoolroomname);
745                         }
746                 }
747         }
748 }
749
750
751 void msiv_load(struct sdm_userdata *u) {
752         char hold_rm[ROOMNAMELEN];
753
754         strcpy(hold_rm, CC->room.QRname);       /* save current room */
755
756         /* Take a spin through the user's personal address book */
757         if (getroom(&CC->room, SIEVERULES) == 0) {
758         
759                 u->config_msgnum = (-1);
760                 strcpy(u->config_roomname, CC->room.QRname);
761                 CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
762                         get_sieve_config_backend, (void *)u );
763
764         }
765
766         if (strcmp(CC->room.QRname, hold_rm)) {
767                 getroom(&CC->room, hold_rm);    /* return to saved room */
768         }
769 }
770
771 void msiv_store(struct sdm_userdata *u) {
772         rewrite_ctdl_sieve_config(u);
773 }
774
775
776 /*
777  * Select the active script.
778  * (Set script_name to an empty string to disable all scripts)
779  * 
780  * Returns 0 on success or nonzero for error.
781  */
782 int msiv_setactive(struct sdm_userdata *u, char *script_name) {
783         int ok = 0;
784         struct sdm_script *s;
785
786         /* First see if the supplied value is ok */
787
788         if (strlen(script_name) == 0) {
789                 ok = 1;
790         }
791         else {
792                 for (s=u->first_script; s!=NULL; s=s->next) {
793                         if (!strcasecmp(s->script_name, script_name)) {
794                                 ok = 1;
795                         }
796                 }
797         }
798
799         if (!ok) return(-1);
800
801         /* Now set the active script */
802         for (s=u->first_script; s!=NULL; s=s->next) {
803                 if (!strcasecmp(s->script_name, script_name)) {
804                         s->script_active = 1;
805                 }
806                 else {
807                         s->script_active = 0;
808                 }
809         }
810         
811         return(0);
812 }
813
814
815 /*
816  * Fetch a script by name.
817  *
818  * Returns NULL if the named script was not found, or a pointer to the script
819  * if it was found.   NOTE: the caller does *not* own the memory returned by
820  * this function.  Copy it if you need to keep it.
821  */
822 char *msiv_getscript(struct sdm_userdata *u, char *script_name) {
823         struct sdm_script *s;
824
825         for (s=u->first_script; s!=NULL; s=s->next) {
826                 if (!strcasecmp(s->script_name, script_name)) {
827                         if (s->script_content != NULL) {
828                                 return (s->script_content);
829                         }
830                 }
831         }
832
833         return(NULL);
834 }
835
836
837 /*
838  * Delete a script by name.
839  *
840  * Returns 0 if the script was deleted.
841  *       1 if the script was not found.
842  *       2 if the script cannot be deleted because it is active.
843  */
844 int msiv_deletescript(struct sdm_userdata *u, char *script_name) {
845         struct sdm_script *s = NULL;
846         struct sdm_script *script_to_delete = NULL;
847
848         for (s=u->first_script; s!=NULL; s=s->next) {
849                 if (!strcasecmp(s->script_name, script_name)) {
850                         script_to_delete = s;
851                         if (s->script_active) {
852                                 return(2);
853                         }
854                 }
855         }
856
857         if (script_to_delete == NULL) return(1);
858
859         if (u->first_script == script_to_delete) {
860                 u->first_script = u->first_script->next;
861         }
862         else for (s=u->first_script; s!=NULL; s=s->next) {
863                 if (s->next == script_to_delete) {
864                         s->next = s->next->next;
865                 }
866         }
867
868         free(script_to_delete->script_content);
869         free(script_to_delete);
870         return(0);
871 }
872
873
874 /*
875  * Add or replace a new script.  
876  * NOTE: after this function returns, "u" owns the memory that "script_content"
877  * was pointing to.
878  */
879 void msiv_putscript(struct sdm_userdata *u, char *script_name, char *script_content) {
880         int replaced = 0;
881         struct sdm_script *s, *sptr;
882
883         for (s=u->first_script; s!=NULL; s=s->next) {
884                 if (!strcasecmp(s->script_name, script_name)) {
885                         if (s->script_content != NULL) {
886                                 free(s->script_content);
887                         }
888                         s->script_content = script_content;
889                         replaced = 1;
890                 }
891         }
892
893         if (replaced == 0) {
894                 sptr = malloc(sizeof(struct sdm_script));
895                 safestrncpy(sptr->script_name, script_name, sizeof sptr->script_name);
896                 sptr->script_content = script_content;
897                 sptr->script_active = 0;
898                 sptr->next = u->first_script;
899                 u->first_script = sptr;
900         }
901 }
902
903
904 /*
905  * Return the list of supported Sieve extensions
906  */
907 char *msiv_listextensions(void) {
908         return "FIXME - this is not done yet";
909 }
910
911
912 /*
913  * Citadel protocol to manage sieve scripts.
914  * This is basically a simplified (read: doesn't resemble IMAP) version
915  * of the 'managesieve' protocol.
916  */
917 void cmd_msiv(char *argbuf) {
918         char subcmd[256];
919         struct sdm_userdata u;
920         char script_name[256];
921         char *script_content = NULL;
922         struct sdm_script *s;
923         int i;
924
925         memset(&u, 0, sizeof(struct sdm_userdata));
926
927         if (CtdlAccessCheck(ac_logged_in)) return;
928         extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
929         msiv_load(&u);
930
931         if (!strcasecmp(subcmd, "putscript")) {
932                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
933                 if (strlen(script_name) > 0) {
934                         cprintf("%d Transmit script now\n", SEND_LISTING);
935                         script_content = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
936                         msiv_putscript(&u, script_name, script_content);
937                 }
938                 else {
939                         cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
940                 }
941         }       
942         
943         else if (!strcasecmp(subcmd, "listscripts")) {
944                 cprintf("%d Scripts:\n", LISTING_FOLLOWS);
945                 for (s=u.first_script; s!=NULL; s=s->next) {
946                         if (s->script_content != NULL) {
947                                 cprintf("%s|%d|\n", s->script_name, s->script_active);
948                         }
949                 }
950                 cprintf("000\n");
951         }
952
953         else if (!strcasecmp(subcmd, "setactive")) {
954                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
955                 if (msiv_setactive(&u, script_name) == 0) {
956                         cprintf("%d ok\n", CIT_OK);
957                 }
958                 else {
959                         cprintf("%d Script '%s' does not exist.\n",
960                                 ERROR + ILLEGAL_VALUE,
961                                 script_name
962                         );
963                 }
964         }
965
966         else if (!strcasecmp(subcmd, "getscript")) {
967                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
968                 script_content = msiv_getscript(&u, script_name);
969                 if (script_content != NULL) {
970                         cprintf("%d Script:\n", SEND_LISTING);
971                         cprintf("%s000\n", script_content);
972                 }
973                 else {
974                         cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
975                 }
976         }
977
978         else if (!strcasecmp(subcmd, "deletescript")) {
979                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
980                 i = msiv_deletescript(&u, script_name);
981                 if (i == 0) {
982                         cprintf("%d ok\n", CIT_OK);
983                 }
984                 else if (i == 1) {
985                         cprintf("%d Script '%s' does not exist.\n",
986                                 ERROR + ILLEGAL_VALUE,
987                                 script_name
988                         );
989                 }
990                 else if (i == 2) {
991                         cprintf("%d Script '%s' is active and cannot be deleted.\n",
992                                 ERROR + ILLEGAL_VALUE,
993                                 script_name
994                         );
995                 }
996                 else {
997                         cprintf("%d unknown error\n", ERROR);
998                 }
999         }
1000
1001         else {
1002                 cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
1003         }
1004
1005         msiv_store(&u);
1006 }
1007
1008
1009
1010 /*
1011  *      We don't really care about dumping the entire credits to the log
1012  *      every time the server is initialized.  The documentation will suffice
1013  *      for that purpose.  We are making a call to sieve2_credits() in order
1014  *      to demonstrate that we have successfully linked in to libsieve.
1015  */
1016 void log_the_sieve2_credits(void) {
1017         char *cred = NULL;
1018
1019         cred = strdup(sieve2_credits());
1020         if (cred == NULL) return;
1021
1022         if (strlen(cred) > 60) {
1023                 strcpy(&cred[55], "...");
1024         }
1025
1026         lprintf(CTDL_INFO, "%s\n",cred);
1027         free(cred);
1028 }
1029
1030
1031
1032 char *serv_sieve_init(void)
1033 {
1034         log_the_sieve2_credits();
1035         CtdlRegisterProtoHook(cmd_msiv, "MSIV", "Manage Sieve scripts");
1036         return "$Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $";
1037 }
1038
1039 #else   /* HAVE_LIBSIEVE */
1040
1041 char *serv_sieve_init(void)
1042 {
1043         lprintf(CTDL_INFO, "This server is missing libsieve.  Mailbox filtering will be disabled.\n");
1044         return "$Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $";
1045 }
1046
1047 #endif  /* HAVE_LIBSIEVE */