* Cleaned up some minor details
[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 };
58
59
60 /*
61  * Callback function to send libSieve trace messages to Citadel log facility
62  * Set ctdl_libsieve_debug=1 to see extremely verbose libSieve trace
63  */
64 int ctdl_debug(sieve2_context_t *s, void *my)
65 {
66         static int ctdl_libsieve_debug = 1;
67
68         if (ctdl_libsieve_debug) {
69                 lprintf(CTDL_DEBUG, "Sieve: level [%d] module [%s] file [%s] function [%s]\n",
70                         sieve2_getvalue_int(s, "level"),
71                         sieve2_getvalue_string(s, "module"),
72                         sieve2_getvalue_string(s, "file"),
73                         sieve2_getvalue_string(s, "function"));
74                 lprintf(CTDL_DEBUG, "       message [%s]\n",
75                         sieve2_getvalue_string(s, "message"));
76         }
77         return SIEVE2_OK;
78 }
79
80
81 /*
82  * Callback function to redirect a message to a different folder
83  */
84 int ctdl_redirect(sieve2_context_t *s, void *my)
85 {
86         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
87
88         lprintf(CTDL_DEBUG, "REDIRECT to <%s> (FIXME complete this)\n",
89                 sieve2_getvalue_string(s, "address"));
90
91         return SIEVE2_OK;
92 }
93
94
95 /*
96  * Callback function to retrieve the sieve script
97  */
98 int ctdl_getscript(sieve2_context_t *s, void *my) {
99
100         lprintf(CTDL_DEBUG, "ctdl_getscript() was called\n");
101
102         sieve2_setvalue_string(s, "script",
103
104                 "    if header :contains [\"From\"]  [\"coyote\"] {             \n"
105                 "        redirect \"acm@frobnitzm.edu\";                        \n"
106                 "    } elsif header :contains \"Subject\" \"$$$\" {             \n"
107                 "        redirect \"postmaster@frobnitzm.edu\";                 \n"
108                 "    } else {                                                   \n"
109                 "        redirect \"field@frobnitzm.edu\";                      \n"
110                 "    }                                                          \n"
111
112         );
113
114         return SIEVE2_OK;
115 }
116
117 /*
118  * Callback function to retrieve message headers
119  */
120 int ctdl_getheaders(sieve2_context_t *s, void *my) {
121
122         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
123
124         lprintf(CTDL_DEBUG, "ctdl_getheaders() was called\n");
125
126         sieve2_setvalue_string(s, "allheaders", cs->rfc822headers);
127         return SIEVE2_OK;
128 }
129
130
131
132 /*
133  * Add a room to the list of those rooms which potentially require sieve processing
134  */
135 void sieve_queue_room(struct ctdlroom *which_room) {
136         struct RoomProcList *ptr;
137
138         ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList));
139         if (ptr == NULL) return;
140
141         safestrncpy(ptr->name, which_room->QRname, sizeof ptr->name);
142         begin_critical_section(S_SIEVELIST);
143         ptr->next = sieve_list;
144         sieve_list = ptr;
145         end_critical_section(S_SIEVELIST);
146 }
147
148
149
150 /*
151  * Perform sieve processing for one message (called by sieve_do_room() for each message)
152  */
153 void sieve_do_msg(long msgnum, void *userdata) {
154         sieve2_context_t *sieve2_context = (sieve2_context_t *) userdata;
155         struct ctdl_sieve my;
156         int res;
157
158         lprintf(CTDL_DEBUG, "Performing sieve processing on msg <%ld>\n", msgnum);
159
160         CC->redirect_buffer = malloc(SIZ);
161         CC->redirect_len = 0;
162         CC->redirect_alloc = SIZ;
163         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ONLY, 0, 1, NULL);
164         my.rfc822headers = CC->redirect_buffer;
165         CC->redirect_buffer = NULL;
166         CC->redirect_len = 0;
167         CC->redirect_alloc = 0;
168
169         sieve2_setvalue_string(sieve2_context, "allheaders", my.rfc822headers);
170         
171         lprintf(CTDL_DEBUG, "Calling sieve2_execute()\n");
172         res = sieve2_execute(sieve2_context, &my);
173         if (res != SIEVE2_OK) {
174                 lprintf(CTDL_CRIT, "sieve2_execute() returned %d: %s\n", res, sieve2_errstr(res));
175         }
176
177         free(my.rfc822headers);
178         my.rfc822headers = NULL;
179
180         return;
181 }
182
183
184 /*
185  * Perform sieve processing for a single room
186  * FIXME ... actually do this instead of just talking about it
187  */
188 void sieve_do_room(char *roomname) {
189         
190         sieve2_context_t *sieve2_context = NULL;        /* Context for sieve parser */
191         int res;                                        /* Return code from libsieve calls */
192
193         /*
194          * CALLBACK REGISTRATION TABLE
195          */
196         sieve2_callback_t ctdl_sieve_callbacks[] = {
197                 { SIEVE2_DEBUG_TRACE,           ctdl_debug       },
198 /*
199                 { SIEVE2_ERRCALL_PARSE,         my_errparse      },
200                 { SIEVE2_ERRCALL_RUNTIME,       my_errexec       },
201                 { SIEVE2_ERRCALL_PARSE,         my_errparse      },
202                 { SIEVE2_ACTION_FILEINTO,       my_fileinto      },
203 */
204                 { SIEVE2_ACTION_REDIRECT,       ctdl_redirect    },
205 /*
206                 { SIEVE2_ACTION_REJECT,         my_reject        },
207                 { SIEVE2_ACTION_NOTIFY,         my_notify        },
208                 { SIEVE2_ACTION_VACATION,       my_vacation      },
209                 { SIEVE2_ACTION_KEEP,           my_fileinto      },     * KEEP is essentially the default case of FILEINTO "INBOX". *
210 */
211                 { SIEVE2_SCRIPT_GETSCRIPT,      ctdl_getscript   },
212                 { SIEVE2_MESSAGE_GETHEADER,     NULL             },     /* We don't support one header at a time. */
213                 { SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders  },     /* libSieve can parse headers itself, so we'll use that. */
214 /*
215                 { SIEVE2_MESSAGE_GETSUBADDRESS, my_getsubaddress },
216                 { SIEVE2_MESSAGE_GETENVELOPE,   my_getenvelope   },
217                 { SIEVE2_MESSAGE_GETBODY,       my_getbody       },
218                 { SIEVE2_MESSAGE_GETSIZE,       my_getsize       },
219 */
220                 { 0 }
221         };
222
223         /* FIXME check to see if this room has any sieve scripts to run */
224
225         lprintf(CTDL_DEBUG, "Performing Sieve processing for <%s>\n", roomname);
226
227         if (getroom(&CC->room, roomname) != 0) {
228                 lprintf(CTDL_CRIT, "ERROR: cannot load <%s>\n", roomname);
229                 return;
230         }
231
232         /* Initialize the Sieve parser */
233         
234         res = sieve2_alloc(&sieve2_context);
235         if (res != SIEVE2_OK) {
236                 lprintf(CTDL_CRIT, "sieve2_alloc() returned %d: %s\n", res, sieve2_errstr(res));
237                 return;
238         }
239
240         res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks);
241         if (res != SIEVE2_OK) {
242                 lprintf(CTDL_CRIT, "sieve2_callbacks() returned %d: %s\n", res, sieve2_errstr(res));
243                 goto BAIL;
244         }
245
246         /* Validate the script */
247
248         res = sieve2_validate(sieve2_context, NULL);
249         if (res != SIEVE2_OK) {
250                 lprintf(CTDL_CRIT, "sieve2_validate() returned %d: %s\n", res, sieve2_errstr(res));
251                 goto BAIL;
252         }
253
254         /* Do something useful */
255         /* CtdlForEachMessage(MSGS_GT, sc.lastsent, NULL, NULL, NULL, */
256         /* FIXME figure out which messages haven't yet been processed by sieve */
257         CtdlForEachMessage(MSGS_LAST, 1, NULL, NULL, NULL,
258                 sieve_do_msg,
259                 (void *) sieve2_context
260         );
261
262 BAIL:
263         res = sieve2_free(&sieve2_context);
264         if (res != SIEVE2_OK) {
265                 lprintf(CTDL_CRIT, "sieve2_free() returned %d: %s\n", res, sieve2_errstr(res));
266         }
267 }
268
269
270 /*
271  * Perform sieve processing for all rooms which require it
272  */
273 void perform_sieve_processing(void) {
274         struct RoomProcList *ptr = NULL;
275
276         if (sieve_list != NULL) {
277                 lprintf(CTDL_DEBUG, "Begin Sieve processing\n");
278                 while (sieve_list != NULL) {
279                         char spoolroomname[ROOMNAMELEN];
280                         safestrncpy(spoolroomname, sieve_list->name, sizeof spoolroomname);
281                         begin_critical_section(S_SIEVELIST);
282
283                         /* pop this record off the list */
284                         ptr = sieve_list;
285                         sieve_list = sieve_list->next;
286                         free(ptr);
287
288                         /* invalidate any duplicate entries to prevent double processing */
289                         for (ptr=sieve_list; ptr!=NULL; ptr=ptr->next) {
290                                 if (!strcasecmp(ptr->name, spoolroomname)) {
291                                         ptr->name[0] = 0;
292                                 }
293                         }
294
295                         end_critical_section(S_SIEVELIST);
296                         if (spoolroomname[0] != 0) {
297                                 sieve_do_room(spoolroomname);
298                         }
299                 }
300         }
301 }
302
303
304
305 /**
306  *      We don't really care about dumping the entire credits to the log
307  *      every time the server is initialized.  The documentation will suffice
308  *      for that purpose.  We are making a call to sieve2_credits() in order
309  *      to demonstrate that we have successfully linked in to libsieve.
310  */
311 void log_the_sieve2_credits(void) {
312         char *cred = NULL;
313
314         cred = strdup(sieve2_credits());
315         if (cred == NULL) return;
316
317         if (strlen(cred) > 60) {
318                 strcpy(&cred[55], "...");
319         }
320
321         lprintf(CTDL_INFO, "%s\n",cred);
322         free(cred);
323 }
324
325
326 char *serv_sieve_init(void)
327 {
328         log_the_sieve2_credits();
329         return "$Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $";
330 }
331
332 #else   /* HAVE_LIBSIEVE */
333
334 char *serv_sieve_init(void)
335 {
336         lprintf(CTDL_INFO, "This server is missing libsieve.  Mailbox filtering will be disabled.\n");
337         return "$Id: serv_sieve.c 3850 2005-09-13 14:00:24Z ajc $";
338 }
339
340 #endif  /* HAVE_LIBSIEVE */