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