]> code.citadel.org Git - citadel.git/blob - citadel/serv_sieve.c
Completed API functions to load and store
[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 "tools.h"
46
47 #ifdef HAVE_LIBSIEVE
48
49 #include "serv_sieve.h"
50
51 struct RoomProcList *sieve_list = NULL;
52
53
54 /*
55  * Callback function to send libSieve trace messages to Citadel log facility
56  * Set ctdl_libsieve_debug=1 to see extremely verbose libSieve trace
57  */
58 int ctdl_debug(sieve2_context_t *s, void *my)
59 {
60         static int ctdl_libsieve_debug = 0;
61
62         if (ctdl_libsieve_debug) {
63                 lprintf(CTDL_DEBUG, "Sieve: level [%d] module [%s] file [%s] function [%s]\n",
64                         sieve2_getvalue_int(s, "level"),
65                         sieve2_getvalue_string(s, "module"),
66                         sieve2_getvalue_string(s, "file"),
67                         sieve2_getvalue_string(s, "function"));
68                 lprintf(CTDL_DEBUG, "       message [%s]\n",
69                         sieve2_getvalue_string(s, "message"));
70         }
71         return SIEVE2_OK;
72 }
73
74
75 /*
76  * Callback function to log script parsing errors
77  */
78 int ctdl_errparse(sieve2_context_t *s, void *my)
79 {
80         lprintf(CTDL_WARNING, "Error in script, line %d: %s\n",
81                 sieve2_getvalue_int(s, "lineno"),
82                 sieve2_getvalue_string(s, "message")
83         );
84         return SIEVE2_OK;
85 }
86
87
88 /*
89  * Callback function to log script execution errors
90  */
91 int ctdl_errexec(sieve2_context_t *s, void *my)
92 {
93         lprintf(CTDL_WARNING, "Error executing script: %s\n",
94                 sieve2_getvalue_string(s, "message")
95         );
96         return SIEVE2_OK;
97 }
98
99
100 /*
101  * Callback function to redirect a message to a different folder
102  */
103 int ctdl_redirect(sieve2_context_t *s, void *my)
104 {
105         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
106         struct CtdlMessage *msg = NULL;
107         struct recptypes *valid = NULL;
108         char recp[256];
109
110         safestrncpy(recp, sieve2_getvalue_string(s, "address"), sizeof recp);
111
112         lprintf(CTDL_DEBUG, "Action is REDIRECT, recipient <%s>\n", recp);
113
114         valid = validate_recipients(recp);
115         if (valid == NULL) {
116                 lprintf(CTDL_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
117                 return SIEVE2_ERROR_BADARGS;
118         }
119         if (valid->num_error > 0) {
120                 lprintf(CTDL_WARNING, "REDIRECT failed: bad recipient <%s>\n", recp);
121                 free(valid);
122                 return SIEVE2_ERROR_BADARGS;
123         }
124
125         msg = CtdlFetchMessage(cs->msgnum, 1);
126         if (msg == NULL) {
127                 lprintf(CTDL_WARNING, "REDIRECT failed: unable to fetch msg %ld\n", cs->msgnum);
128                 free(valid);
129                 return SIEVE2_ERROR_BADARGS;
130         }
131
132         CtdlSubmitMsg(msg, valid, NULL);
133         cs->actiontaken = 1;
134         free(valid);
135         CtdlFreeMessage(msg);
136         return SIEVE2_OK;
137 }
138
139
140 /*
141  * Callback function to indicate that a message *will* be kept in the inbox
142  */
143 int ctdl_keep(sieve2_context_t *s, void *my)
144 {
145         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
146         
147         lprintf(CTDL_DEBUG, "Action is KEEP\n");
148
149         cs->keep = 1;
150         cs->actiontaken = 1;
151         return SIEVE2_OK;
152 }
153
154
155 /*
156  * Callback function to file a message into a different mailbox
157  */
158 int ctdl_fileinto(sieve2_context_t *s, void *my)
159 {
160         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
161         const char *dest_folder = sieve2_getvalue_string(s, "mailbox");
162         int c;
163         char foldername[256];
164         char original_room_name[ROOMNAMELEN];
165
166         lprintf(CTDL_DEBUG, "Action is FILEINTO, destination is <%s>\n", dest_folder);
167
168         /* FILEINTO 'INBOX' is the same thing as KEEP */
169         if ( (!strcasecmp(dest_folder, "INBOX")) || (!strcasecmp(dest_folder, MAILROOM)) ) {
170                 cs->keep = 1;
171                 cs->actiontaken = 1;
172                 return SIEVE2_OK;
173         }
174
175         /* Remember what room we came from */
176         safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
177
178         /* First try a mailbox name match (check personal mail folders first) */
179         snprintf(foldername, sizeof foldername, "%010ld.%s", cs->usernum, dest_folder);
180         c = getroom(&CC->room, foldername);
181
182         /* Then a regular room name match (public and private rooms) */
183         if (c < 0) {
184                 safestrncpy(foldername, dest_folder, sizeof foldername);
185                 c = getroom(&CC->room, foldername);
186         }
187
188         if (c < 0) {
189                 lprintf(CTDL_WARNING, "FILEINTO failed: target <%s> does not exist\n", dest_folder);
190                 return SIEVE2_ERROR_BADARGS;
191         }
192
193         /* Yes, we actually have to go there */
194         usergoto(NULL, 0, 0, NULL, NULL);
195
196         c = CtdlSaveMsgPointersInRoom(NULL, &cs->msgnum, 1, 0, NULL);
197
198         /* Go back to the room we came from */
199         if (strcasecmp(original_room_name, CC->room.QRname)) {
200                 usergoto(original_room_name, 0, 0, NULL, NULL);
201         }
202
203         if (c == 0) {
204                 cs->actiontaken = 1;
205                 return SIEVE2_OK;
206         }
207         else {
208                 return SIEVE2_ERROR_BADARGS;
209         }
210 }
211
212
213 /*
214  * Callback function to indicate that a message should be rejected
215  * FIXME implement this
216  */
217 int ctdl_reject(sieve2_context_t *s, void *my)
218 {
219         lprintf(CTDL_DEBUG, "Action is REJECT\n");
220         return SIEVE2_ERROR_UNSUPPORTED;
221 }
222
223
224 /*
225  * Callback function to indicate that the user should be notified
226  * FIXME implement this
227  */
228 int ctdl_notify(sieve2_context_t *s, void *my)
229 {
230         lprintf(CTDL_DEBUG, "Action is NOTIFY\n");
231         return SIEVE2_ERROR_UNSUPPORTED;
232 }
233
234
235 /*
236  * Callback function to indicate that a vacation message should be generated
237  * FIXME implement this
238  */
239 int ctdl_vacation(sieve2_context_t *s, void *my)
240 {
241         lprintf(CTDL_DEBUG, "Action is VACATION\n");
242         return SIEVE2_ERROR_UNSUPPORTED;
243 }
244
245
246 /*
247  * Callback function to parse addresses per local system convention
248  * FIXME implement this
249  */
250 int ctdl_getsubaddress(sieve2_context_t *s, void *my)
251 {
252         return SIEVE2_ERROR_UNSUPPORTED;
253 }
254
255
256 /*
257  * Callback function to parse message envelope
258  * FIXME implement this
259  */
260 int ctdl_getenvelope(sieve2_context_t *s, void *my)
261 {
262         return SIEVE2_ERROR_UNSUPPORTED;
263 }
264
265
266 /*
267  * Callback function to fetch message body
268  * FIXME implement this
269  */
270 int ctdl_getbody(sieve2_context_t *s, void *my)
271 {
272         return SIEVE2_ERROR_UNSUPPORTED;
273 }
274
275
276 /*
277  * Callback function to fetch message size
278  * FIXME implement this
279  */
280 int ctdl_getsize(sieve2_context_t *s, void *my)
281 {
282         return SIEVE2_ERROR_UNSUPPORTED;
283 }
284
285
286 /*
287  * Callback function to indicate that a message should be discarded.
288  */
289 int ctdl_discard(sieve2_context_t *s, void *my)
290 {
291         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
292
293         lprintf(CTDL_DEBUG, "Action is DISCARD\n");
294
295         /* Yes, this is really all there is to it.  Since we are not setting "keep" to 1,
296          * the message will be discarded because "some other action" was successfully taken.
297          */
298         cs->actiontaken = 1;
299         return SIEVE2_OK;
300 }
301
302
303
304 /*
305  * Callback function to retrieve the sieve script
306  */
307 int ctdl_getscript(sieve2_context_t *s, void *my) {
308         struct sdm_script *sptr;
309         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
310
311         lprintf(CTDL_DEBUG, "ctdl_getscript() was called.  config_msgnum=%ld config_lastproc=%ld\n",
312                 cs->u->config_msgnum, cs->u->lastproc
313         );
314
315         for (sptr=cs->u->first_script; sptr!=NULL; sptr=sptr->next) {
316                 lprintf(CTDL_DEBUG, "Can we use script '%s' ?  %s.\n",
317                         sptr->script_name,
318                         ( (sptr->script_active > 0) ? "yes" : "no" )
319                 );
320                 if (sptr->script_active > 0) {
321                         lprintf(CTDL_DEBUG, "ctdl_getscript() is using script '%s'\n", sptr->script_name);
322                         sieve2_setvalue_string(s, "script", sptr->script_content);
323                         return SIEVE2_OK;
324                 }
325         }
326                 
327         lprintf(CTDL_DEBUG, "ctdl_getscript() found no active script\n");
328         return SIEVE2_ERROR_GETSCRIPT;
329 }
330
331 /*
332  * Callback function to retrieve message headers
333  */
334 int ctdl_getheaders(sieve2_context_t *s, void *my) {
335
336         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
337
338         lprintf(CTDL_DEBUG, "ctdl_getheaders() was called\n");
339
340         sieve2_setvalue_string(s, "allheaders", cs->rfc822headers);
341         return SIEVE2_OK;
342 }
343
344
345
346 /*
347  * Add a room to the list of those rooms which potentially require sieve processing
348  */
349 void sieve_queue_room(struct ctdlroom *which_room) {
350         struct RoomProcList *ptr;
351
352         ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
353         if (ptr == NULL) return;
354
355         safestrncpy(ptr->name, which_room->QRname, sizeof ptr->name);
356         begin_critical_section(S_SIEVELIST);
357         ptr->next = sieve_list;
358         sieve_list = ptr;
359         end_critical_section(S_SIEVELIST);
360 }
361
362
363
364 /*
365  * Perform sieve processing for one message (called by sieve_do_room() for each message)
366  */
367 void sieve_do_msg(long msgnum, void *userdata) {
368         struct sdm_userdata *u = (struct sdm_userdata *) userdata;
369         sieve2_context_t *sieve2_context = u->sieve2_context;
370         struct ctdl_sieve my;
371         int res;
372
373         lprintf(CTDL_DEBUG, "Performing sieve processing on msg <%ld>\n", msgnum);
374
375         CC->redirect_buffer = malloc(SIZ);
376         CC->redirect_len = 0;
377         CC->redirect_alloc = SIZ;
378         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ONLY, 0, 1, NULL);
379         my.rfc822headers = CC->redirect_buffer;
380         CC->redirect_buffer = NULL;
381         CC->redirect_len = 0;
382         CC->redirect_alloc = 0;
383
384         my.keep = 0;            /* Don't keep a copy in the inbox unless a callback tells us to do so */
385         my.actiontaken = 0;     /* Keep track of whether any actions were successfully taken */
386         my.usernum = atol(CC->room.QRname);     /* Keep track of the owner of the room's namespace */
387         my.msgnum = msgnum;     /* Keep track of the message number in our local store */
388         my.u = u;               /* Hand off a pointer to the rest of this info */
389
390         sieve2_setvalue_string(sieve2_context, "allheaders", my.rfc822headers);
391         
392         lprintf(CTDL_DEBUG, "Calling sieve2_execute()\n");
393         res = sieve2_execute(sieve2_context, &my);
394         if (res != SIEVE2_OK) {
395                 lprintf(CTDL_CRIT, "sieve2_execute() returned %d: %s\n", res, sieve2_errstr(res));
396         }
397
398         free(my.rfc822headers);
399         my.rfc822headers = NULL;
400
401         /*
402          * Delete the message from the inbox unless either we were told not to, or
403          * if no other action was successfully taken.
404          */
405         if ( (!my.keep) && (my.actiontaken) ) {
406                 lprintf(CTDL_DEBUG, "keep is 0 -- deleting message from inbox\n");
407                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "", 0);
408         }
409
410         lprintf(CTDL_DEBUG, "Completed sieve processing on msg <%ld>\n", msgnum);
411         u->lastproc = msgnum;
412
413         return;
414 }
415
416
417
418 /*
419  * Given the on-disk representation of our Sieve config, load
420  * it into an in-memory data structure.
421  */
422 void parse_sieve_config(char *conf, struct sdm_userdata *u) {
423         char *ptr;
424         char *c;
425         char keyword[256];
426         struct sdm_script *sptr;
427
428         ptr = conf;
429         while (c = ptr, ptr = bmstrcasestr(ptr, CTDLSIEVECONFIGSEPARATOR), ptr != NULL) {
430                 *ptr = 0;
431                 ptr += strlen(CTDLSIEVECONFIGSEPARATOR);
432
433                 extract_token(keyword, c, 0, '|', sizeof keyword);
434                 lprintf(CTDL_DEBUG, "CONFIG: <%s>\n", keyword);
435
436                 if (!strcasecmp(keyword, "lastproc")) {
437                         u->lastproc = extract_long(c, 1);
438                 }
439
440                 else if (!strcasecmp(keyword, "script")) {
441                         sptr = malloc(sizeof(struct sdm_script));
442                         extract_token(sptr->script_name, c, 1, '|', sizeof sptr->script_name);
443                         sptr->script_active = extract_int(c, 2);
444                         remove_token(c, 0, '|');
445                         remove_token(c, 0, '|');
446                         remove_token(c, 0, '|');
447                         sptr->script_content = strdup(c);
448                         sptr->next = u->first_script;
449                         u->first_script = sptr;
450                 }
451
452                 /* ignore unknown keywords */
453         }
454         lprintf(CTDL_DEBUG, "done parsing config\n");
455 }
456
457 /*
458  * We found the Sieve configuration for this user.
459  * Now do something with it.
460  */
461 void get_sieve_config_backend(long msgnum, void *userdata) {
462         struct sdm_userdata *u = (struct sdm_userdata *) userdata;
463         struct CtdlMessage *msg;
464         char *conf;
465
466         u->config_msgnum = msgnum;
467         msg = CtdlFetchMessage(msgnum, 1);
468         if (msg == NULL) {
469                 u->config_msgnum = (-1) ;
470                 return;
471         }
472
473         conf = msg->cm_fields['M'];
474         msg->cm_fields['M'] = NULL;
475         CtdlFreeMessage(msg);
476
477         if (conf != NULL) {
478                 parse_sieve_config(conf, u);
479                 free(conf);
480         }
481
482 }
483
484
485 /* 
486  * Write our citadel sieve config back to disk
487  */
488 void rewrite_ctdl_sieve_config(struct sdm_userdata *u) {
489         char *text;
490         struct sdm_script *sptr;
491
492
493         text = malloc(1024);
494         snprintf(text, 1024,
495                 "Content-type: application/x-citadel-sieve-config\n"
496                 "\n"
497                 CTDLSIEVECONFIGSEPARATOR
498                 "lastproc|%ld"
499                 CTDLSIEVECONFIGSEPARATOR
500         ,
501                 u->lastproc
502         );
503
504         while (u->first_script != NULL) {
505                 text = realloc(text, strlen(text) + strlen(u->first_script->script_content) + 256);
506                 sprintf(&text[strlen(text)], "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR,
507                         u->first_script->script_name,
508                         u->first_script->script_active,
509                         u->first_script->script_content
510                 );
511                 sptr = u->first_script;
512                 u->first_script = u->first_script->next;
513                 free(sptr->script_content);
514                 free(sptr);
515         }
516
517         /* Save the config */
518         quickie_message("Citadel", NULL, u->config_roomname,
519                         text,
520                         4,
521                         "Sieve configuration"
522         );
523
524         /* And delete the old one */
525         if (u->config_msgnum > 0) {
526                 CtdlDeleteMessages(u->config_roomname, &u->config_msgnum, 1, "", 0);
527         }
528
529 }
530
531
532
533 /*
534  * Perform sieve processing for a single room
535  */
536 void sieve_do_room(char *roomname) {
537         
538         struct sdm_userdata u;
539         sieve2_context_t *sieve2_context = NULL;        /* Context for sieve parser */
540         int res;                                        /* Return code from libsieve calls */
541         long orig_lastproc = 0;
542
543         memset(&u, 0, sizeof u);
544
545         /*
546          * This is our callback registration table for libSieve.
547          */
548         sieve2_callback_t ctdl_sieve_callbacks[] = {
549                 { SIEVE2_ACTION_REJECT,         ctdl_reject             },
550                 { SIEVE2_ACTION_NOTIFY,         ctdl_notify             },
551                 { SIEVE2_ACTION_VACATION,       ctdl_vacation           },
552                 { SIEVE2_ERRCALL_PARSE,         ctdl_errparse           },
553                 { SIEVE2_ERRCALL_RUNTIME,       ctdl_errexec            },
554                 { SIEVE2_ACTION_FILEINTO,       ctdl_fileinto           },
555                 { SIEVE2_ACTION_REDIRECT,       ctdl_redirect           },
556                 { SIEVE2_ACTION_DISCARD,        ctdl_discard            },
557                 { SIEVE2_ACTION_KEEP,           ctdl_keep               },
558                 { SIEVE2_SCRIPT_GETSCRIPT,      ctdl_getscript          },
559                 { SIEVE2_DEBUG_TRACE,           ctdl_debug              },
560                 { SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders         },
561                 { SIEVE2_MESSAGE_GETSUBADDRESS, ctdl_getsubaddress      },
562                 { SIEVE2_MESSAGE_GETENVELOPE,   ctdl_getenvelope        },
563                 { SIEVE2_MESSAGE_GETBODY,       ctdl_getbody            },
564                 { SIEVE2_MESSAGE_GETSIZE,       ctdl_getsize            },
565                 { 0 }
566         };
567
568         /* See if the user who owns this 'mailbox' has any Sieve scripts that
569          * require execution.
570          */
571         snprintf(u.config_roomname, sizeof u.config_roomname, "%010ld.%s", atol(roomname), SIEVERULES);
572         if (getroom(&CC->room, u.config_roomname) != 0) {
573                 lprintf(CTDL_DEBUG, "<%s> does not exist.  No processing is required.\n", u.config_roomname);
574                 return;
575         }
576
577         /*
578          * Find the sieve scripts and control record and do something
579          */
580         u.config_msgnum = (-1);
581         CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
582                 get_sieve_config_backend, (void *)&u );
583
584         if (u.config_msgnum < 0) {
585                 lprintf(CTDL_DEBUG, "No Sieve rules exist.  No processing is required.\n");
586                 return;
587         }
588
589         lprintf(CTDL_DEBUG, "Rules found.  Performing Sieve processing for <%s>\n", roomname);
590
591         if (getroom(&CC->room, roomname) != 0) {
592                 lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", roomname);
593                 return;
594         }
595
596         /* Initialize the Sieve parser */
597         
598         res = sieve2_alloc(&sieve2_context);
599         if (res != SIEVE2_OK) {
600                 lprintf(CTDL_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
601                 return;
602         }
603
604         res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
605         if (res != SIEVE2_OK) {
606                 lprintf(CTDL_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
607                 goto BAIL;
608         }
609
610         /* Validate the script */
611
612         struct ctdl_sieve my;           /* dummy ctdl_sieve struct just to pass "u" slong */
613         memset(&my, 0, sizeof my);
614         my.u = &u;
615         res = sieve2_validate(sieve2_context, &my);
616         if (res != SIEVE2_OK) {
617                 lprintf(CTDL_CRIT, "sieve2_validate() returned %d: %s\n", res, sieve2_errstr(res));
618                 goto BAIL;
619         }
620
621         /* Do something useful */
622         u.sieve2_context = sieve2_context;
623         orig_lastproc = u.lastproc;
624         CtdlForEachMessage(MSGS_GT, u.lastproc, NULL, NULL, NULL,
625                 sieve_do_msg,
626                 (void *) &u
627         );
628
629 BAIL:
630         res = sieve2_free(&sieve2_context);
631         if (res != SIEVE2_OK) {
632                 lprintf(CTDL_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
633         }
634
635         /* Rewrite the config if we have to */
636         if (u.lastproc > orig_lastproc) {
637                 rewrite_ctdl_sieve_config(&u);
638         }
639 }
640
641
642 /*
643  * Perform sieve processing for all rooms which require it
644  */
645 void perform_sieve_processing(void) {
646         struct RoomProcList *ptr = NULL;
647
648         if (sieve_list != NULL) {
649                 lprintf(CTDL_DEBUG, "Begin Sieve processing\n");
650                 while (sieve_list != NULL) {
651                         char spoolroomname[ROOMNAMELEN];
652                         safestrncpy(spoolroomname, sieve_list->name, sizeof spoolroomname);
653                         begin_critical_section(S_SIEVELIST);
654
655                         /* pop this record off the list */
656                         ptr = sieve_list;
657                         sieve_list = sieve_list->next;
658                         free(ptr);
659
660                         /* invalidate any duplicate entries to prevent double processing */
661                         for (ptr=sieve_list; ptr!=NULL; ptr=ptr->next) {
662                                 if (!strcasecmp(ptr->name, spoolroomname)) {
663                                         ptr->name[0] = 0;
664                                 }
665                         }
666
667                         end_critical_section(S_SIEVELIST);
668                         if (spoolroomname[0] != 0) {
669                                 sieve_do_room(spoolroomname);
670                         }
671                 }
672         }
673 }
674
675
676 void msiv_load(struct sdm_userdata *u) {
677         char hold_rm[ROOMNAMELEN];
678
679         strcpy(hold_rm, CC->room.QRname);       /* save current room */
680
681         /* Take a spin through the user's personal address book */
682         if (getroom(&CC->room, SIEVERULES) == 0) {
683         
684                 u->config_msgnum = (-1);
685                 strcpy(u->config_roomname, CC->room.QRname);
686                 CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL,
687                         get_sieve_config_backend, (void *)u );
688
689         }
690
691         if (strcmp(CC->room.QRname, hold_rm)) {
692                 getroom(&CC->room, hold_rm);    /* return to saved room */
693         }
694 }
695
696 void msiv_store(struct sdm_userdata *u) {
697         rewrite_ctdl_sieve_config(u);
698 }
699
700
701 /*
702  * Fetch a script by name.
703  *
704  * Returns NULL if the named script was not found, or a pointer to the script
705  * if it was found.   NOTE: the caller does *not* own the memory returned by
706  * this function.  Copy it if you need to keep it.
707  */
708 char *msiv_getscript(struct sdm_userdata *u, char *script_name) {
709         struct sdm_script *s;
710
711         for (s=u->first_script; s!=NULL; s=s->next) {
712                 if (!strcasecmp(s->script_name, script_name)) {
713                         if (s->script_content != NULL) {
714                                 return (s->script_content);
715                         }
716                 }
717         }
718
719         return(NULL);
720 }
721
722
723
724 /*
725  * Add or replace a new script.  
726  * NOTE: after this function returns, "u" owns the memory that "script_content"
727  * was pointing to.
728  */
729 void msiv_putscript(struct sdm_userdata *u, char *script_name, char *script_content) {
730         int replaced = 0;
731         struct sdm_script *s, *sptr;
732
733         for (s=u->first_script; s!=NULL; s=s->next) {
734                 if (!strcasecmp(s->script_name, script_name)) {
735                         if (s->script_content != NULL) {
736                                 free(s->script_content);
737                         }
738                         s->script_content = script_content;
739                         replaced = 1;
740                 }
741         }
742
743         if (replaced == 0) {
744                 sptr = malloc(sizeof(struct sdm_script));
745                 safestrncpy(sptr->script_name, script_name, sizeof sptr->script_name);
746                 sptr->script_content = script_content;
747                 sptr->script_active = 0;
748                 sptr->next = u->first_script;
749                 u->first_script = sptr;
750         }
751 }
752
753
754 /*
755  * Citadel protocol to manage sieve scripts.
756  * This is basically a simplified (read: doesn't resemble IMAP) version
757  * of the 'managesieve' protocol.
758  */
759 void cmd_msiv(char *argbuf) {
760         char subcmd[256];
761         struct sdm_userdata u;
762         char script_name[256];
763         char *script_content = NULL;
764
765         memset(&u, 0, sizeof(struct sdm_userdata));
766
767         if (CtdlAccessCheck(ac_logged_in)) return;
768         extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
769         msiv_load(&u);
770
771         if (!strcasecmp(subcmd, "putscript")) {
772                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
773                 if (strlen(script_name) > 0) {
774                         cprintf("%d Transmit script now\n", SEND_LISTING);
775                         script_content = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
776                         msiv_putscript(&u, script_name, script_content);
777                 }
778                 else {
779                         cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
780                 }
781         }       
782         
783         else if (!strcasecmp(subcmd, "listscripts")) {
784         }
785
786         else if (!strcasecmp(subcmd, "setactive")) {
787         }
788
789         else if (!strcasecmp(subcmd, "getscript")) {
790                 extract_token(script_name, argbuf, 1, '|', sizeof script_name);
791                 script_content = msiv_getscript(&u, script_name);
792                 if (script_content != NULL) {
793                         cprintf("%d Script:\n", SEND_LISTING);
794                         cprintf("%s000\n", script_content);
795                 }
796                 else {
797                         cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE);
798                 }
799         }
800
801         else if (!strcasecmp(subcmd, "deletescript")) {
802         }
803
804         else {
805                 cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
806         }
807
808         msiv_store(&u);
809 }
810
811
812
813 /*
814  *      We don't really care about dumping the entire credits to the log
815  *      every time the server is initialized.  The documentation will suffice
816  *      for that purpose.  We are making a call to sieve2_credits() in order
817  *      to demonstrate that we have successfully linked in to libsieve.
818  */
819 void log_the_sieve2_credits(void) {
820         char *cred = NULL;
821
822         cred = strdup(sieve2_credits());
823         if (cred == NULL) return;
824
825         if (strlen(cred) > 60) {
826                 strcpy(&cred[55], "...");
827         }
828
829         lprintf(CTDL_INFO, "%s\n",cred);
830         free(cred);
831 }
832
833
834
835 char *serv_sieve_init(void)
836 {
837         log_the_sieve2_credits();
838         CtdlRegisterProtoHook(cmd_msiv, "MSIV", "Manage Sieve scripts");
839         return "$Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $";
840 }
841
842 #else   /* HAVE_LIBSIEVE */
843
844 char *serv_sieve_init(void)
845 {
846         lprintf(CTDL_INFO, "This server is missing libsieve.  Mailbox filtering will be disabled.\n");
847         return "$Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $";
848 }
849
850 #endif  /* HAVE_LIBSIEVE */