* Actions for KEEP and DISCARD completed
[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 };
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
115         lprintf(CTDL_DEBUG, "REDIRECT to <%s> (FIXME complete this)\n",
116                 sieve2_getvalue_string(s, "address"));
117
118         return SIEVE2_OK;
119 }
120
121
122 /*
123  * Callback function to indicate that a message *will* be kept in the inbox
124  */
125 int ctdl_keep(sieve2_context_t *s, void *my)
126 {
127         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
128         
129         lprintf(CTDL_DEBUG, "Action is KEEP\n");
130
131         cs->keep = 1;
132         cs->actiontaken = 1;
133         return SIEVE2_OK;
134 }
135
136
137 /*
138  * Callback function to file a message into a different mailbox
139  */
140 int ctdl_fileinto(sieve2_context_t *s, void *my)
141 {
142         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
143         const char *dest_folder = sieve2_getvalue_string(s, "mailbox");
144
145         lprintf(CTDL_DEBUG, "Action is FILEINTO, destination is <%s>\n", dest_folder);
146
147
148         /* FILEINTO 'INBOX' is the same thing as KEEP */
149         if ( (!strcasecmp(dest_folder, "INBOX")) || (!strcasecmp(dest_folder, MAILROOM)) ) {
150                 cs->keep = 1;
151                 cs->actiontaken = 1;
152                 return SIEVE2_OK;
153         }
154
155         /* FIXME finish this to file it in another arbitrary room */
156         return SIEVE2_ERROR_BADARGS;
157 }
158
159
160 /*
161  * Callback function to indicate that a message should be discarded.
162  */
163 int ctdl_discard(sieve2_context_t *s, void *my)
164 {
165         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
166
167         lprintf(CTDL_DEBUG, "Action is DISCARD\n");
168
169         /* Yes, this is really all there is to it.  Since we are not setting "keep" to 1,
170          * the message will be discarded because "some other action" was successfully taken.
171          */
172         cs->actiontaken = 1;
173         return SIEVE2_OK;
174 }
175
176
177
178 /*
179  * Callback function to retrieve the sieve script
180  */
181 int ctdl_getscript(sieve2_context_t *s, void *my) {
182
183         lprintf(CTDL_DEBUG, "ctdl_getscript() was called\n");
184
185         sieve2_setvalue_string(s, "script",
186                 
187                 "require \"fileinto\";                                          \n"
188                 "    if header :contains [\"From\"]  [\"coyote\"] {             \n"
189                 "        redirect \"acm@frobnitzm.edu\";                        \n"
190                 "    } elsif header :contains \"Subject\" \"JIHAD\" {           \n"
191                 "        fileinto \"jihad\";                                    \n"
192                 "    } else {                                                   \n"
193                 "        keep;                                                  \n"
194                 "    }                                                          \n"
195
196         );
197
198         return SIEVE2_OK;
199 }
200
201 /*
202  * Callback function to retrieve message headers
203  */
204 int ctdl_getheaders(sieve2_context_t *s, void *my) {
205
206         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
207
208         lprintf(CTDL_DEBUG, "ctdl_getheaders() was called\n");
209
210         sieve2_setvalue_string(s, "allheaders", cs->rfc822headers);
211         return SIEVE2_OK;
212 }
213
214
215
216 /*
217  * Add a room to the list of those rooms which potentially require sieve processing
218  */
219 void sieve_queue_room(struct ctdlroom *which_room) {
220         struct RoomProcList *ptr;
221
222         ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
223         if (ptr == NULL) return;
224
225         safestrncpy(ptr->name, which_room->QRname, sizeof ptr->name);
226         begin_critical_section(S_SIEVELIST);
227         ptr->next = sieve_list;
228         sieve_list = ptr;
229         end_critical_section(S_SIEVELIST);
230 }
231
232
233
234 /*
235  * Perform sieve processing for one message (called by sieve_do_room() for each message)
236  */
237 void sieve_do_msg(long msgnum, void *userdata) {
238         sieve2_context_t *sieve2_context = (sieve2_context_t *) userdata;
239         struct ctdl_sieve my;
240         int res;
241
242         lprintf(CTDL_DEBUG, "Performing sieve processing on msg <%ld>\n", msgnum);
243
244         CC->redirect_buffer = malloc(SIZ);
245         CC->redirect_len = 0;
246         CC->redirect_alloc = SIZ;
247         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ONLY, 0, 1, NULL);
248         my.rfc822headers = CC->redirect_buffer;
249         CC->redirect_buffer = NULL;
250         CC->redirect_len = 0;
251         CC->redirect_alloc = 0;
252
253         my.keep = 0;            /* Don't keep a copy in the inbox unless a callback tells us to do so */
254         my.actiontaken = 0;     /* Keep track of whether any actions were successfully taken */
255
256         sieve2_setvalue_string(sieve2_context, "allheaders", my.rfc822headers);
257         
258         lprintf(CTDL_DEBUG, "Calling sieve2_execute()\n");
259         res = sieve2_execute(sieve2_context, &my);
260         if (res != SIEVE2_OK) {
261                 lprintf(CTDL_CRIT, "sieve2_execute() returned %d: %s\n", res, sieve2_errstr(res));
262         }
263
264         free(my.rfc822headers);
265         my.rfc822headers = NULL;
266
267         /*
268          * Delete the message from the inbox unless either we were told not to, or
269          * if no other action was successfully taken.
270          */
271         if ( (!my.keep) && (my.actiontaken) ) {
272                 lprintf(CTDL_DEBUG, "keep is 0 -- deleting message from inbox\n");
273                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "", 0);
274         }
275
276         lprintf(CTDL_DEBUG, "Completed sieve processing on msg <%ld>\n", msgnum);
277
278         return;
279 }
280
281
282 /*
283  * Perform sieve processing for a single room
284  * FIXME ... actually do this instead of just talking about it
285  */
286 void sieve_do_room(char *roomname) {
287         
288         sieve2_context_t *sieve2_context = NULL;        /* Context for sieve parser */
289         int res;                                        /* Return code from libsieve calls */
290
291         /*
292          * CALLBACK REGISTRATION TABLE
293          */
294         sieve2_callback_t ctdl_sieve_callbacks[] = {
295                 { SIEVE2_DEBUG_TRACE,           ctdl_debug       },
296 /*
297                 { SIEVE2_ACTION_REJECT,         my_reject        },
298                 { SIEVE2_ACTION_NOTIFY,         my_notify        },
299                 { SIEVE2_ACTION_VACATION,       my_vacation      },
300 */
301                 { SIEVE2_ERRCALL_PARSE,         ctdl_errparse    },
302                 { SIEVE2_ERRCALL_RUNTIME,       ctdl_errexec     },
303                 { SIEVE2_ACTION_FILEINTO,       ctdl_fileinto    },
304                 { SIEVE2_ACTION_REDIRECT,       ctdl_redirect    },
305                 { SIEVE2_ACTION_DISCARD,        ctdl_discard     },
306                 { SIEVE2_ACTION_KEEP,           ctdl_keep        },
307                 { SIEVE2_SCRIPT_GETSCRIPT,      ctdl_getscript   },
308                 { SIEVE2_MESSAGE_GETHEADER,     NULL             },     /* We don't support one header at a time. */
309                 { SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders  },     /* libSieve can parse headers itself, so we'll use that. */
310 /*
311                 { SIEVE2_MESSAGE_GETSUBADDRESS, my_getsubaddress },
312                 { SIEVE2_MESSAGE_GETENVELOPE,   my_getenvelope   },
313                 { SIEVE2_MESSAGE_GETBODY,       my_getbody       },
314                 { SIEVE2_MESSAGE_GETSIZE,       my_getsize       },
315 */
316                 { 0 }
317         };
318
319         /* FIXME check to see if this room has any sieve scripts to run */
320
321         lprintf(CTDL_DEBUG, "Performing Sieve processing for <%s>\n", roomname);
322
323         if (getroom(&CC->room, roomname) != 0) {
324                 lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", roomname);
325                 return;
326         }
327
328         /* Initialize the Sieve parser */
329         
330         res = sieve2_alloc(&sieve2_context);
331         if (res != SIEVE2_OK) {
332                 lprintf(CTDL_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
333                 return;
334         }
335
336         res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
337         if (res != SIEVE2_OK) {
338                 lprintf(CTDL_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
339                 goto BAIL;
340         }
341
342         /* Validate the script */
343
344         res = sieve2_validate(sieve2_context, NULL);
345         if (res != SIEVE2_OK) {
346                 lprintf(CTDL_CRIT, "sieve2_validate() returned %d: %s\n", res, sieve2_errstr(res));
347                 goto BAIL;
348         }
349
350         /* Do something useful */
351         /* CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL, NULL, */
352         /* FIXME figure out which messages haven't yet been processed by sieve */
353         CtdlForEachMessage(MSGS_LAST, 1, NULL, NULL, NULL,
354                 sieve_do_msg,
355                 (void *) sieve2_context
356         );
357
358 BAIL:
359         res = sieve2_free(&sieve2_context);
360         if (res != SIEVE2_OK) {
361                 lprintf(CTDL_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
362         }
363 }
364
365
366 /*
367  * Perform sieve processing for all rooms which require it
368  */
369 void perform_sieve_processing(void) {
370         struct RoomProcList *ptr = NULL;
371
372         if (sieve_list != NULL) {
373                 lprintf(CTDL_DEBUG, "Begin Sieve processing\n");
374                 while (sieve_list != NULL) {
375                         char spoolroomname[ROOMNAMELEN];
376                         safestrncpy(spoolroomname, sieve_list->name, sizeof spoolroomname);
377                         begin_critical_section(S_SIEVELIST);
378
379                         /* pop this record off the list */
380                         ptr = sieve_list;
381                         sieve_list = sieve_list->next;
382                         free(ptr);
383
384                         /* invalidate any duplicate entries to prevent double processing */
385                         for (ptr=sieve_list; ptr!=NULL; ptr=ptr->next) {
386                                 if (!strcasecmp(ptr->name, spoolroomname)) {
387                                         ptr->name[0] = 0;
388                                 }
389                         }
390
391                         end_critical_section(S_SIEVELIST);
392                         if (spoolroomname[0] != 0) {
393                                 sieve_do_room(spoolroomname);
394                         }
395                 }
396         }
397 }
398
399
400
401 /**
402  *      We don't really care about dumping the entire credits to the log
403  *      every time the server is initialized.  The documentation will suffice
404  *      for that purpose.  We are making a call to sieve2_credits() in order
405  *      to demonstrate that we have successfully linked in to libsieve.
406  */
407 void log_the_sieve2_credits(void) {
408         char *cred = NULL;
409
410         cred = strdup(sieve2_credits());
411         if (cred == NULL) return;
412
413         if (strlen(cred) > 60) {
414                 strcpy(&cred[55], "...");
415         }
416
417         lprintf(CTDL_INFO, "%s\n",cred);
418         free(cred);
419 }
420
421
422 char *serv_sieve_init(void)
423 {
424         log_the_sieve2_credits();
425         return "$Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $";
426 }
427
428 #else   /* HAVE_LIBSIEVE */
429
430 char *serv_sieve_init(void)
431 {
432         lprintf(CTDL_INFO, "This server is missing libsieve.  Mailbox filtering will be disabled.\n");
433         return "$Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $";
434 }
435
436 #endif  /* HAVE_LIBSIEVE */