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