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