Field comparison code is functionally complete
[citadel.git] / citadel / modules / inboxrules / serv_inboxrules.c
1 /*
2  * Inbox handling rules
3  *
4  * Copyright (c) 1987-2020 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include "sysdep.h"
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <stdio.h>
19 #include <fcntl.h>
20 #include <ctype.h>
21 #include <pwd.h>
22 #include <errno.h>
23 #include <sys/types.h>
24
25 #if TIME_WITH_SYS_TIME
26 # include <sys/time.h>
27 # include <time.h>
28 #else
29 # if HAVE_SYS_TIME_H
30 #  include <sys/time.h>
31 # else
32 #  include <time.h>
33 # endif
34 #endif
35
36 #include <sys/wait.h>
37 #include <string.h>
38 #include <limits.h>
39 #include <libcitadel.h>
40 #include "citadel.h"
41 #include "server.h"
42 #include "citserver.h"
43 #include "support.h"
44 #include "config.h"
45 #include "database.h"
46 #include "msgbase.h"
47 #include "internet_addressing.h"
48 #include "ctdl_module.h"
49
50
51 #if 0
52
53 /*
54  * Callback function to redirect a message to a different folder
55  */
56 int ctdl_redirect(void)
57 {
58         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
59         struct CtdlMessage *msg = NULL;
60         recptypes *valid = NULL;
61         char recp[256];
62
63         safestrncpy(recp, sieve2_getvalue_string(s, "address"), sizeof recp);
64
65         syslog(LOG_DEBUG, "Action is REDIRECT, recipient <%s>", recp);
66
67         valid = validate_recipients(recp, NULL, 0);
68         if (valid == NULL) {
69                 syslog(LOG_WARNING, "REDIRECT failed: bad recipient <%s>", recp);
70                 return SIEVE2_ERROR_BADARGS;
71         }
72         if (valid->num_error > 0) {
73                 syslog(LOG_WARNING, "REDIRECT failed: bad recipient <%s>", recp);
74                 free_recipients(valid);
75                 return SIEVE2_ERROR_BADARGS;
76         }
77
78         msg = CtdlFetchMessage(cs->msgnum, 1);
79         if (msg == NULL) {
80                 syslog(LOG_WARNING, "REDIRECT failed: unable to fetch msg %ld", cs->msgnum);
81                 free_recipients(valid);
82                 return SIEVE2_ERROR_BADARGS;
83         }
84
85         CtdlSubmitMsg(msg, valid, NULL, 0);
86         cs->cancel_implicit_keep = 1;
87         free_recipients(valid);
88         CM_Free(msg);
89         return SIEVE2_OK;
90 }
91
92
93 /*
94  * Callback function to indicate that a message *will* be kept in the inbox
95  */
96 int ctdl_keep(sieve2_context_t *s, void *my)
97 {
98         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
99         
100         syslog(LOG_DEBUG, "Action is KEEP");
101
102         cs->keep = 1;
103         cs->cancel_implicit_keep = 1;
104         return SIEVE2_OK;
105 }
106
107
108 /*
109  * Callback function to file a message into a different mailbox
110  */
111 int ctdl_fileinto(sieve2_context_t *s, void *my)
112 {
113         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
114         const char *dest_folder = sieve2_getvalue_string(s, "mailbox");
115         int c;
116         char foldername[256];
117         char original_room_name[ROOMNAMELEN];
118
119         syslog(LOG_DEBUG, "Action is FILEINTO, destination is <%s>", dest_folder);
120
121         /* FILEINTO 'INBOX' is the same thing as KEEP */
122         if ( (!strcasecmp(dest_folder, "INBOX")) || (!strcasecmp(dest_folder, MAILROOM)) ) {
123                 cs->keep = 1;
124                 cs->cancel_implicit_keep = 1;
125                 return SIEVE2_OK;
126         }
127
128         /* Remember what room we came from */
129         safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name);
130
131         /* First try a mailbox name match (check personal mail folders first) */
132         snprintf(foldername, sizeof foldername, "%010ld.%s", cs->usernum, dest_folder);
133         c = CtdlGetRoom(&CC->room, foldername);
134
135         /* Then a regular room name match (public and private rooms) */
136         if (c != 0) {
137                 safestrncpy(foldername, dest_folder, sizeof foldername);
138                 c = CtdlGetRoom(&CC->room, foldername);
139         }
140
141         if (c != 0) {
142                 syslog(LOG_WARNING, "FILEINTO failed: target <%s> does not exist", dest_folder);
143                 return SIEVE2_ERROR_BADARGS;
144         }
145
146         /* Yes, we actually have to go there */
147         CtdlUserGoto(NULL, 0, 0, NULL, NULL, NULL, NULL);
148
149         c = CtdlSaveMsgPointersInRoom(NULL, &cs->msgnum, 1, 0, NULL, 0);
150
151         /* Go back to the room we came from */
152         if (strcasecmp(original_room_name, CC->room.QRname)) {
153                 CtdlUserGoto(original_room_name, 0, 0, NULL, NULL, NULL, NULL);
154         }
155
156         if (c == 0) {
157                 cs->cancel_implicit_keep = 1;
158                 return SIEVE2_OK;
159         }
160         else {
161                 return SIEVE2_ERROR_BADARGS;
162         }
163 }
164
165
166 /*
167  * Callback function to indicate that a message should be discarded.
168  */
169 int ctdl_discard(sieve2_context_t *s, void *my)
170 {
171         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
172
173         syslog(LOG_DEBUG, "Action is DISCARD");
174
175         /* Cancel the implicit keep.  That's all there is to it. */
176         cs->cancel_implicit_keep = 1;
177         return SIEVE2_OK;
178 }
179
180
181 /*
182  * Callback function to indicate that a message should be rejected
183  */
184 int ctdl_reject(sieve2_context_t *s, void *my)
185 {
186         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
187         char *reject_text = NULL;
188
189         syslog(LOG_DEBUG, "Action is REJECT");
190
191         /* If we don't know who sent the message, do a DISCARD instead. */
192         if (IsEmptyStr(cs->sender)) {
193                 syslog(LOG_INFO, "Unknown sender.  Doing DISCARD instead of REJECT.");
194                 return ctdl_discard(s, my);
195         }
196
197         /* Assemble the reject message. */
198         reject_text = malloc(strlen(sieve2_getvalue_string(s, "message")) + 1024);
199         if (reject_text == NULL) {
200                 return SIEVE2_ERROR_FAIL;
201         }
202
203         sprintf(reject_text, 
204                 "Content-type: text/plain\n"
205                 "\n"
206                 "The message was refused by the recipient's mail filtering program.\n"
207                 "The reason given was as follows:\n"
208                 "\n"
209                 "%s\n"
210                 "\n"
211         ,
212                 sieve2_getvalue_string(s, "message")
213         );
214
215         quickie_message(        /* This delivers the message */
216                 NULL,
217                 cs->envelope_to,
218                 cs->sender,
219                 NULL,
220                 reject_text,
221                 FMT_RFC822,
222                 "Delivery status notification"
223         );
224
225         free(reject_text);
226         cs->cancel_implicit_keep = 1;
227         return SIEVE2_OK;
228 }
229
230
231 /*
232  * Callback function to indicate that a vacation message should be generated
233  */
234 int ctdl_vacation(sieve2_context_t *s, void *my)
235 {
236         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
237         struct sdm_vacation *vptr;
238         int days = 1;
239         const char *message;
240         char *vacamsg_text = NULL;
241         char vacamsg_subject[1024];
242
243         syslog(LOG_DEBUG, "Action is VACATION");
244
245         message = sieve2_getvalue_string(s, "message");
246         if (message == NULL) return SIEVE2_ERROR_BADARGS;
247
248         if (sieve2_getvalue_string(s, "subject") != NULL) {
249                 safestrncpy(vacamsg_subject, sieve2_getvalue_string(s, "subject"), sizeof vacamsg_subject);
250         }
251         else {
252                 snprintf(vacamsg_subject, sizeof vacamsg_subject, "Re: %s", cs->subject);
253         }
254
255         days = sieve2_getvalue_int(s, "days");
256         if (days < 1) days = 1;
257         if (days > MAX_VACATION) days = MAX_VACATION;
258
259         /* Check to see whether we've already alerted this sender that we're on vacation. */
260         for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
261                 if (!strcasecmp(vptr->fromaddr, cs->sender)) {
262                         if ( (time(NULL) - vptr->timestamp) < (days * 86400) ) {
263                                 syslog(LOG_DEBUG, "Already alerted <%s> recently.", cs->sender);
264                                 return SIEVE2_OK;
265                         }
266                 }
267         }
268
269         /* Assemble the reject message. */
270         vacamsg_text = malloc(strlen(message) + 1024);
271         if (vacamsg_text == NULL) {
272                 return SIEVE2_ERROR_FAIL;
273         }
274
275         sprintf(vacamsg_text, 
276                 "Content-type: text/plain charset=utf-8\n"
277                 "\n"
278                 "%s\n"
279                 "\n"
280         ,
281                 message
282         );
283
284         quickie_message(        /* This delivers the message */
285                 NULL,
286                 cs->envelope_to,
287                 cs->sender,
288                 NULL,
289                 vacamsg_text,
290                 FMT_RFC822,
291                 vacamsg_subject
292         );
293
294         free(vacamsg_text);
295
296         /* Now update the list to reflect the fact that we've alerted this sender.
297          * If they're already in the list, just update the timestamp.
298          */
299         for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) {
300                 if (!strcasecmp(vptr->fromaddr, cs->sender)) {
301                         vptr->timestamp = time(NULL);
302                         return SIEVE2_OK;
303                 }
304         }
305
306         /* If we get to this point, create a new record.
307          */
308         vptr = malloc(sizeof(struct sdm_vacation));
309         memset(vptr, 0, sizeof(struct sdm_vacation));
310         vptr->timestamp = time(NULL);
311         safestrncpy(vptr->fromaddr, cs->sender, sizeof vptr->fromaddr);
312         vptr->next = cs->u->first_vacation;
313         cs->u->first_vacation = vptr;
314
315         return SIEVE2_OK;
316 }
317
318
319 /*
320  * Callback function to parse message envelope
321  */
322 int ctdl_getenvelope(sieve2_context_t *s, void *my)
323 {
324         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
325
326         syslog(LOG_DEBUG, "Action is GETENVELOPE");
327         syslog(LOG_DEBUG, "EnvFrom: %s", cs->envelope_from);
328         syslog(LOG_DEBUG, "EnvTo: %s", cs->envelope_to);
329
330         if (cs->envelope_from != NULL) {
331                 if ((cs->envelope_from[0] != '@')&&(cs->envelope_from[strlen(cs->envelope_from)-1] != '@')) {
332                         sieve2_setvalue_string(s, "from", cs->envelope_from);
333                 }
334                 else {
335                         sieve2_setvalue_string(s, "from", "invalid_envelope_from@example.org");
336                 }
337         }
338         else {
339                 sieve2_setvalue_string(s, "from", "null_envelope_from@example.org");
340         }
341
342
343         if (cs->envelope_to != NULL) {
344                 if ((cs->envelope_to[0] != '@') && (cs->envelope_to[strlen(cs->envelope_to)-1] != '@')) {
345                         sieve2_setvalue_string(s, "to", cs->envelope_to);
346                 }
347                 else {
348                         sieve2_setvalue_string(s, "to", "invalid_envelope_to@example.org");
349                 }
350         }
351         else {
352                 sieve2_setvalue_string(s, "to", "null_envelope_to@example.org");
353         }
354
355         return SIEVE2_OK;
356 }
357
358
359
360 /*
361  * Callback function to fetch message size
362  */
363 int ctdl_getsize(sieve2_context_t *s, void *my)
364 {
365         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
366         struct MetaData smi;
367
368         GetMetaData(&smi, cs->msgnum);
369         
370         if (smi.meta_rfc822_length > 0L) {
371                 sieve2_setvalue_int(s, "size", (int)smi.meta_rfc822_length);
372                 return SIEVE2_OK;
373         }
374
375         return SIEVE2_ERROR_UNSUPPORTED;
376 }
377
378
379 /*
380  * Return a pointer to the active Sieve script.
381  * (Caller does NOT own the memory and should not free the returned pointer.)
382  */
383 char *get_active_script(struct sdm_userdata *u) {
384         struct sdm_script *sptr;
385
386         for (sptr=u->first_script; sptr!=NULL; sptr=sptr->next) {
387                 if (sptr->script_active > 0) {
388                         syslog(LOG_DEBUG, "get_active_script() is using script '%s'", sptr->script_name);
389                         return(sptr->script_content);
390                 }
391         }
392
393         syslog(LOG_DEBUG, "get_active_script() found no active script");
394         return(NULL);
395 }
396
397
398 /*
399  * Callback function to retrieve the sieve script
400  */
401 int ctdl_getscript(sieve2_context_t *s, void *my) {
402         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
403
404         char *active_script = get_active_script(cs->u);
405         if (active_script != NULL) {
406                 sieve2_setvalue_string(s, "script", active_script);
407                 return SIEVE2_OK;
408         }
409
410         return SIEVE2_ERROR_GETSCRIPT;
411 }
412
413
414 /*
415  * Callback function to retrieve message headers
416  */
417 int ctdl_getheaders(sieve2_context_t *s, void *my) {
418
419         struct ctdl_sieve *cs = (struct ctdl_sieve *)my;
420
421         syslog(LOG_DEBUG, "ctdl_getheaders() was called");
422         sieve2_setvalue_string(s, "allheaders", cs->rfc822headers);
423         return SIEVE2_OK;
424 }
425
426
427 /*
428  * Perform sieve processing for one message (called by sieve_do_room() for each message)
429  */
430 void inbox_do_msg(long msgnum, void *userdata) {
431         struct sdm_userdata *u = (struct sdm_userdata *) userdata;
432         sieve2_context_t *sieve2_context;
433         struct ctdl_sieve my;
434         int res;
435         struct CtdlMessage *msg;
436         int i;
437         size_t headers_len = 0;
438         int len = 0;
439
440         if (u == NULL) {
441                 syslog(LOG_ERR, "Can't process message <%ld> without userdata!", msgnum);
442                 return;
443         }
444
445         sieve2_context = u->sieve2_context;
446
447         syslog(LOG_DEBUG, "Performing sieve processing on msg <%ld>", msgnum);
448
449         /*
450          * Make sure you include message body so you can get those second-level headers ;)
451          */
452         msg = CtdlFetchMessage(msgnum, 1);
453         if (msg == NULL) return;
454
455         /*
456          * Grab the message headers so we can feed them to libSieve.
457          * Use HEADERS_ONLY rather than HEADERS_FAST in order to include second-level headers.
458          */
459         CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
460         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, 0);
461         headers_len = StrLength(CC->redirect_buffer);
462         my.rfc822headers = SmashStrBuf(&CC->redirect_buffer);
463
464         /*
465          * libSieve clobbers the stack if it encounters badly formed
466          * headers.  Sanitize our headers by stripping nonprintable
467          * characters.
468          */
469         for (i=0; i<headers_len; ++i) {
470                 if (!isascii(my.rfc822headers[i])) {
471                         my.rfc822headers[i] = '_';
472                 }
473         }
474
475         my.keep = 0;                            /* Set to 1 to declare an *explicit* keep */
476         my.cancel_implicit_keep = 0;            /* Some actions will cancel the implicit keep */
477         my.usernum = atol(CC->room.QRname);     /* Keep track of the owner of the room's namespace */
478         my.msgnum = msgnum;                     /* Keep track of the message number in our local store */
479         my.u = u;                               /* Hand off a pointer to the rest of this info */
480
481         /* Keep track of the recipient so we can do handling based on it later */
482         process_rfc822_addr(msg->cm_fields[eRecipient], my.recp_user, my.recp_node, my.recp_name);
483
484         /* Keep track of the sender so we can use it for REJECT and VACATION responses */
485         if (!CM_IsEmpty(msg, erFc822Addr)) {
486                 safestrncpy(my.sender, msg->cm_fields[erFc822Addr], sizeof my.sender);
487         }
488         else if (!CM_IsEmpty(msg, eAuthor)) {
489                 safestrncpy(my.sender, msg->cm_fields[eAuthor], sizeof my.sender);
490         }
491         else {
492                 strcpy(my.sender, "");
493         }
494
495         /* Keep track of the subject so we can use it for VACATION responses */
496         if (!CM_IsEmpty(msg, eMsgSubject)) {
497                 safestrncpy(my.subject, msg->cm_fields[eMsgSubject], sizeof my.subject);
498         }
499         else {
500                 strcpy(my.subject, "");
501         }
502
503         /* Keep track of the envelope-from address (use body-from if not found) */
504         if (!CM_IsEmpty(msg, eMessagePath)) {
505                 safestrncpy(my.envelope_from, msg->cm_fields[eMessagePath], sizeof my.envelope_from);
506                 stripallbut(my.envelope_from, '<', '>');
507         }
508         else if (!CM_IsEmpty(msg, erFc822Addr)) {
509                 safestrncpy(my.envelope_from, msg->cm_fields[erFc822Addr], sizeof my.envelope_from);
510                 stripallbut(my.envelope_from, '<', '>');
511         }
512         else {
513                 strcpy(my.envelope_from, "");
514         }
515
516         len = strlen(my.envelope_from);
517         for (i=0; i<len; ++i) {
518                 if (isspace(my.envelope_from[i])) my.envelope_from[i] = '_';
519         }
520         if (haschar(my.envelope_from, '@') == 0) {
521                 strcat(my.envelope_from, "@");
522                 strcat(my.envelope_from, CtdlGetConfigStr("c_fqdn"));
523         }
524
525         /* Keep track of the envelope-to address (use body-to if not found) */
526         if (!CM_IsEmpty(msg, eenVelopeTo)) {
527                 safestrncpy(my.envelope_to, msg->cm_fields[eenVelopeTo], sizeof my.envelope_to);
528                 stripallbut(my.envelope_to, '<', '>');
529         }
530         else if (!CM_IsEmpty(msg, eRecipient)) {
531                 safestrncpy(my.envelope_to, msg->cm_fields[eRecipient], sizeof my.envelope_to);
532                 stripallbut(my.envelope_to, '<', '>');
533         }
534         else {
535                 strcpy(my.envelope_to, "");
536         }
537
538         len = strlen(my.envelope_to);
539         for (i=0; i<len; ++i) {
540                 if (isspace(my.envelope_to[i])) my.envelope_to[i] = '_';
541         }
542         if (haschar(my.envelope_to, '@') == 0) {
543                 strcat(my.envelope_to, "@");
544                 strcat(my.envelope_to, CtdlGetConfigStr("c_fqdn"));
545         }
546
547         CM_Free(msg);
548         
549         syslog(LOG_DEBUG, "Calling sieve2_execute()");
550         res = sieve2_execute(sieve2_context, &my);
551         if (res != SIEVE2_OK) {
552                 syslog(LOG_ERR, "sieve2_execute() returned %d: %s", res, sieve2_errstr(res));
553         }
554
555         free(my.rfc822headers);
556         my.rfc822headers = NULL;
557
558         /*
559          * Delete the message from the inbox unless either we were told not to, or
560          * if no other action was successfully taken.
561          */
562         if ( (!my.keep) && (my.cancel_implicit_keep) ) {
563                 syslog(LOG_DEBUG, "keep is 0 -- deleting message from inbox");
564                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
565         }
566
567         syslog(LOG_DEBUG, "Completed sieve processing on msg <%ld>", msgnum);
568         u->lastproc = msgnum;
569
570         return;
571 }
572
573
574
575 /*
576  * Given the on-disk representation of our Sieve config, load
577  * it into an in-memory data structure.
578  */
579 void parse_sieve_config(char *conf, struct sdm_userdata *u) {
580         char *ptr;
581         char *c, *vacrec;
582         char keyword[256];
583         struct sdm_script *sptr;
584         struct sdm_vacation *vptr;
585
586         ptr = conf;
587         while (c = ptr, ptr = bmstrcasestr(ptr, CTDLSIEVECONFIGSEPARATOR), ptr != NULL) {
588                 *ptr = 0;
589                 ptr += strlen(CTDLSIEVECONFIGSEPARATOR);
590
591                 extract_token(keyword, c, 0, '|', sizeof keyword);
592
593                 if (!strcasecmp(keyword, "lastproc")) {
594                         u->lastproc = extract_long(c, 1);
595                 }
596
597                 else if (!strcasecmp(keyword, "script")) {
598                         sptr = malloc(sizeof(struct sdm_script));
599                         extract_token(sptr->script_name, c, 1, '|', sizeof sptr->script_name);
600                         sptr->script_active = extract_int(c, 2);
601                         remove_token(c, 0, '|');
602                         remove_token(c, 0, '|');
603                         remove_token(c, 0, '|');
604                         sptr->script_content = strdup(c);
605                         sptr->next = u->first_script;
606                         u->first_script = sptr;
607                 }
608
609                 else if (!strcasecmp(keyword, "vacation")) {
610
611                         if (c != NULL) while (vacrec=c, c=strchr(c, '\n'), (c != NULL)) {
612
613                                 *c = 0;
614                                 ++c;
615
616                                 if (strncasecmp(vacrec, "vacation|", 9)) {
617                                         vptr = malloc(sizeof(struct sdm_vacation));
618                                         extract_token(vptr->fromaddr, vacrec, 0, '|', sizeof vptr->fromaddr);
619                                         vptr->timestamp = extract_long(vacrec, 1);
620                                         vptr->next = u->first_vacation;
621                                         u->first_vacation = vptr;
622                                 }
623                         }
624                 }
625
626                 /* ignore unknown keywords */
627         }
628 }
629
630
631
632
633
634 /* 
635  * Write our citadel sieve config back to disk
636  * 
637  * (Set yes_write_to_disk to nonzero to make it actually write the config;
638  * otherwise it just frees the data structures.)
639  */
640 void rewrite_ctdl_sieve_config(struct sdm_userdata *u, int yes_write_to_disk) {
641         StrBuf *text;
642         struct sdm_script *sptr;
643         struct sdm_vacation *vptr;
644         
645         text = NewStrBufPlain(NULL, SIZ);
646         StrBufPrintf(text,
647                      "Content-type: application/x-citadel-sieve-config\n"
648                      "\n"
649                      CTDLSIEVECONFIGSEPARATOR
650                      "lastproc|%ld"
651                      CTDLSIEVECONFIGSEPARATOR
652                      ,
653                      u->lastproc
654                 );
655
656         while (u->first_script != NULL) {
657                 StrBufAppendPrintf(text,
658                                    "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR,
659                                    u->first_script->script_name,
660                                    u->first_script->script_active,
661                                    u->first_script->script_content
662                         );
663                 sptr = u->first_script;
664                 u->first_script = u->first_script->next;
665                 free(sptr->script_content);
666                 free(sptr);
667         }
668
669         if (u->first_vacation != NULL) {
670
671                 StrBufAppendPrintf(text, "vacation|\n");
672                 while (u->first_vacation != NULL) {
673                         if ( (time(NULL) - u->first_vacation->timestamp) < (MAX_VACATION * 86400)) {
674                                 StrBufAppendPrintf(text, "%s|%ld\n",
675                                                    u->first_vacation->fromaddr,
676                                                    u->first_vacation->timestamp
677                                         );
678                         }
679                         vptr = u->first_vacation;
680                         u->first_vacation = u->first_vacation->next;
681                         free(vptr);
682                 }
683                 StrBufAppendPrintf(text, CTDLSIEVECONFIGSEPARATOR);
684         }
685
686         if (yes_write_to_disk)
687         {
688                 /* Save the config */
689                 quickie_message("Citadel", NULL, NULL, u->config_roomname,
690                                 ChrPtr(text),
691                                 4,
692                                 "Sieve configuration"
693                 );
694                 
695                 /* And delete the old one */
696                 if (u->config_msgnum > 0) {
697                         CtdlDeleteMessages(u->config_roomname, &u->config_msgnum, 1, "");
698                 }
699         }
700
701         FreeStrBuf (&text);
702
703 }
704
705
706 /*
707  * This is our callback registration table for libSieve.
708  */
709 sieve2_callback_t ctdl_sieve_callbacks[] = {
710         { SIEVE2_ACTION_REJECT,         ctdl_reject             },
711         { SIEVE2_ACTION_VACATION,       ctdl_vacation           },
712         { SIEVE2_ERRCALL_PARSE,         ctdl_errparse           },
713         { SIEVE2_ERRCALL_RUNTIME,       ctdl_errexec            },
714         { SIEVE2_ACTION_FILEINTO,       ctdl_fileinto           },
715         { SIEVE2_ACTION_REDIRECT,       ctdl_redirect           },
716         { SIEVE2_ACTION_DISCARD,        ctdl_discard            },
717         { SIEVE2_ACTION_KEEP,           ctdl_keep               },
718         { SIEVE2_SCRIPT_GETSCRIPT,      ctdl_getscript          },
719         { SIEVE2_DEBUG_TRACE,           ctdl_debug              },
720         { SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders         },
721         { SIEVE2_MESSAGE_GETSIZE,       ctdl_getsize            },
722         { SIEVE2_MESSAGE_GETENVELOPE,   ctdl_getenvelope        },
723         { 0 }
724 };
725
726
727
728
729
730
731
732
733
734
735
736 #endif
737
738
739 /*
740  * The next sections are enums and keys that drive the serialize/deserialize functions for the inbox rules/state configuration.
741  */
742
743 // Fields to be compared
744 enum {
745         field_from,             
746         field_tocc,             
747         field_subject,  
748         field_replyto,  
749         field_sender,   
750         field_resentfrom,       
751         field_resentto, 
752         field_envfrom,  
753         field_envto,    
754         field_xmailer,  
755         field_xspamflag,        
756         field_xspamstatus,      
757         field_listid,   
758         field_size,             
759         field_all
760 };
761 char *field_keys[] = {
762         "from",
763         "tocc",
764         "subject",
765         "replyto",
766         "sender",
767         "resentfrom",
768         "resentto",
769         "envfrom",
770         "envto",
771         "xmailer",
772         "xspamflag",
773         "xspamstatus",
774         "listid",
775         "size",
776         "all"
777 };
778
779 // Field comparison operators
780 enum {
781         fcomp_contains,
782         fcomp_notcontains,
783         fcomp_is,
784         fcomp_isnot,
785         fcomp_matches,
786         fcomp_notmatches
787 };
788 char *fcomp_keys[] = {
789         "contains",
790         "notcontains",
791         "is",
792         "isnot",
793         "matches",
794         "notmatches"
795 };
796
797 // Actions
798 enum {
799         action_keep,
800         action_discard,
801         action_reject,
802         action_fileinto,
803         action_redirect,
804         action_vacation
805 };
806 char *action_keys[] = {
807         "keep",
808         "discard",
809         "reject",
810         "fileinto",
811         "redirect",
812         "vacation"
813 };
814
815 // Size comparison operators
816 enum {
817         scomp_larger,
818         scomp_smaller
819 };
820 char *scomp_keys[] = {
821         "larger",
822         "smaller"
823 };
824
825 // Final actions
826 enum {
827         final_continue,
828         final_stop
829 };
830 char *final_keys[] = {
831         "continue",
832         "stop"
833 };
834
835 // This data structure represents ONE inbox rule within the configuration.
836 struct irule {
837         int compared_field;
838         int field_compare_op;
839         char compared_value[128];
840         int size_compare_op;
841         long compared_size;
842         int action;
843         char file_into[ROOMNAMELEN];
844         char redirect_to[1024];
845         char autoreply_message[SIZ];
846         int final_action;
847 };
848
849 // This data structure represents the entire inbox rules configuration AND current state for a single user.
850 struct inboxrules {
851         long lastproc;
852         int num_rules;
853         struct irule *rules;
854 };
855
856
857 // Destructor for 'struct inboxrules'
858 void free_inbox_rules(struct inboxrules *ibr) {
859         free(ibr->rules);
860         free(ibr);
861 }
862
863
864 // Constructor for 'struct inboxrules' that deserializes the configuration from text input.
865 struct inboxrules *deserialize_inbox_rules(char *serialized_rules) {
866         int i;
867
868         if (!serialized_rules) {
869                 return NULL;
870         }
871
872         /* Make a copy of the supplied buffer because we're going to shit all over it with strtok_r() */
873         char *sr = strdup(serialized_rules);
874         if (!sr) {
875                 return NULL;
876         }
877
878         struct inboxrules *ibr = malloc(sizeof(struct inboxrules));
879         if (ibr == NULL) {
880                 return NULL;
881         }
882         memset(ibr, 0, sizeof(struct inboxrules));
883
884         char *token; 
885         char *rest = sr;
886         while ((token = strtok_r(rest, "\n", &rest))) {
887
888                 // For backwards compatibility, "# WEBCIT_RULE" is an alias for "rule".
889                 // Prior to version 930, WebCit converted its rules to Sieve scripts, but saved the rules as comments for later re-editing.
890                 // Now, the rules hidden in the comments become the real rules.
891                 if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
892                         strcpy(token, "rule|"); 
893                         strcpy(&token[5], &token[14]);
894                 }
895
896                 // Lines containing actual rules are double-serialized with Base64.  It seemed like a good idea at the time :(
897                 if (!strncasecmp(token, "rule|", 5)) {
898                         remove_token(&token[5], 0, '|');
899                         char *decoded_rule = malloc(strlen(token));
900                         CtdlDecodeBase64(decoded_rule, &token[5], strlen(&token[5]));
901                         ibr->num_rules++;
902                         ibr->rules = realloc(ibr->rules, (sizeof(struct irule) * ibr->num_rules));
903                         struct irule *new_rule = &ibr->rules[ibr->num_rules - 1];
904                         memset(new_rule, 0, sizeof(struct irule));
905
906                         // We have a rule , now parse it
907                         char rtoken[SIZ];
908                         int nt = num_tokens(decoded_rule, '|');
909                         for (int t=0; t<nt; ++t) {
910                                 extract_token(rtoken, decoded_rule, t, '|', sizeof(rtoken));
911                                 striplt(rtoken);
912                                 switch(t) {
913                                         case 1:                                                                 // field to compare
914                                                 for (i=0; i<=field_all; ++i) {
915                                                         if (!strcasecmp(rtoken, field_keys[i])) {
916                                                                 new_rule->compared_field = i;
917                                                         }
918                                                 }
919                                                 break;
920                                         case 2:                                                                 // field comparison operation
921                                                 for (i=0; i<=fcomp_notmatches; ++i) {
922                                                         if (!strcasecmp(rtoken, fcomp_keys[i])) {
923                                                                 new_rule->field_compare_op = i;
924                                                         }
925                                                 }
926                                                 break;
927                                         case 3:                                                                 // field comparison value
928                                                 safestrncpy(new_rule->compared_value, rtoken, sizeof(new_rule->compared_value));
929                                                 break;
930                                         case 4:                                                                 // size comparison operation
931                                                 for (i=0; i<=scomp_smaller; ++i) {
932                                                         if (!strcasecmp(rtoken, scomp_keys[i])) {
933                                                                 new_rule->size_compare_op = i;
934                                                         }
935                                                 }
936                                                 break;
937                                         case 5:                                                                 // size comparison value
938                                                 new_rule->compared_size = atol(rtoken);
939                                                 break;
940                                         case 6:                                                                 // action
941                                                 for (i=0; i<=action_vacation; ++i) {
942                                                         if (!strcasecmp(rtoken, action_keys[i])) {
943                                                                 new_rule->action = i;
944                                                         }
945                                                 }
946                                                 break;
947                                         case 7:                                                                 // file into (target room)
948                                                 safestrncpy(new_rule->file_into, rtoken, sizeof(new_rule->file_into));
949                                                 break;
950                                         case 8:                                                                 // redirect to (target address)
951                                                 safestrncpy(new_rule->redirect_to, rtoken, sizeof(new_rule->redirect_to));
952                                                 break;
953                                         case 9:                                                                 // autoreply message
954                                                 safestrncpy(new_rule->autoreply_message, rtoken, sizeof(new_rule->autoreply_message));
955                                                 break;
956                                         case 10:                                                                // final_action;
957                                                 for (i=0; i<=final_stop; ++i) {
958                                                         if (!strcasecmp(rtoken, final_keys[i])) {
959                                                                 new_rule->final_action = i;
960                                                         }
961                                                 }
962                                                 break;
963                                         default:
964                                                 break;
965                                 }
966                         }
967                         free(decoded_rule);
968
969                         // if we re-serialized this now, what would it look like?
970                         syslog(LOG_DEBUG, "test reserialize: 0|%s|%s|%s|%s|%ld|%s|%s|%s|%s|%s",
971                                 field_keys[new_rule->compared_field],
972                                 fcomp_keys[new_rule->field_compare_op],
973                                 new_rule->compared_value,
974                                 scomp_keys[new_rule->size_compare_op],
975                                 new_rule->compared_size,
976                                 action_keys[new_rule->action],
977                                 new_rule->file_into,
978                                 new_rule->redirect_to,
979                                 new_rule->autoreply_message,
980                                 final_keys[new_rule->final_action]
981                         );
982                         // delete the above after moving it to a reserialize function
983
984                 }
985
986                 // "lastproc" indicates the newest message number in the inbox that was previously processed by our inbox rules.
987                 else if (!strncasecmp(token, "lastproc|", 5)) {
988                         ibr->lastproc = atol(&token[9]);
989                 }
990
991         }
992
993         free(sr);               // free our copy of the source buffer that has now been trashed with null bytes...
994         return(ibr);            // and return our complex data type to the caller.
995 }
996
997
998 /*
999  * Process a single message.  We know the room, the user, the rules, the message number, etc.
1000  */
1001 void inbox_do_msg(long msgnum, void *userdata) {
1002         struct inboxrules *ii = (struct inboxrules *) userdata;
1003         struct CtdlMessage *msg = NULL;         // If we are loading a message to process, put it here.
1004         int headers_loaded = 0;                 // Did we load the headers yet?  Do it only once.
1005         int body_loaded = 0;                    // Did we load the message body yet?  Do it only once.
1006         int metadata_loaded = 0;                // Did we load the metadata yet?  Do it only once.
1007         struct MetaData smi;                    // If we are loading the metadata to compare, put it here.
1008         int rule_activated = 0;                 // On each rule, this is set if the compare succeeds and the rule activates.
1009         char compare_me[SIZ];                   // On each rule, we will store the field to be compared here.
1010         int i;
1011
1012         syslog(LOG_DEBUG, "inboxrules: processing message #%ld which is higher than %ld, we are in %s", msgnum, ii->lastproc, CC->room.QRname);
1013
1014         if (ii->num_rules <= 0) {
1015                 syslog(LOG_DEBUG, "inboxrules: rule set is empty");
1016                 return;
1017         }
1018
1019         for (i=0; i<ii->num_rules; ++i) {
1020                 syslog(LOG_DEBUG, "inboxrules: processing rule %d is %s", i, field_keys[ ii->rules[i].compared_field ]);
1021                 rule_activated = 0;
1022
1023                 // Before doing a field compare, check to see if we have the correct parts of the message in memory.
1024
1025                 switch(ii->rules[i].compared_field) {
1026                         // These fields require loading only the top-level headers
1027                         case field_from:                // From:
1028                         case field_tocc:                // To: or Cc:
1029                         case field_subject:             // Subject:
1030                         case field_replyto:             // Reply-to:
1031                         case field_listid:              // List-ID:
1032                         case field_envto:               // Envelope-to:
1033                         case field_envfrom:             // Return-path:
1034                                 if (!headers_loaded) {
1035                                         syslog(LOG_DEBUG, "inboxrules: loading headers for message %ld", msgnum);
1036                                         msg = CtdlFetchMessage(msgnum, 0);
1037                                         if (!msg) {
1038                                                 return;
1039                                         }
1040                                         headers_loaded = 1;
1041                                 }
1042                                 break;
1043                         // These fields are not stored as Citadel headers, and therefore require a full message load.
1044                         case field_sender:
1045                         case field_resentfrom:
1046                         case field_resentto:
1047                         case field_xmailer:
1048                         case field_xspamflag:
1049                         case field_xspamstatus:
1050                                 if (!body_loaded) {
1051                                         syslog(LOG_DEBUG, "inboxrules: loading all of message %ld", msgnum);
1052                                         if (msg != NULL) {
1053                                                 CM_Free(msg);
1054                                         }
1055                                         msg = CtdlFetchMessage(msgnum, 1);
1056                                         if (!msg) {
1057                                                 return;
1058                                         }
1059                                         headers_loaded = 1;
1060                                         body_loaded = 1;
1061                                 }
1062                                 break;
1063                         case field_size:
1064                                 if (!metadata_loaded) {
1065                                         syslog(LOG_DEBUG, "inboxrules: loading metadata for message %ld", msgnum);
1066                                         GetMetaData(&smi, msgnum);
1067                                         metadata_loaded = 1;
1068                                 }
1069                                 break;
1070                         case field_all:
1071                                 syslog(LOG_DEBUG, "inboxrules: this is an always-on rule");
1072                                 break;
1073                         default:
1074                                 syslog(LOG_DEBUG, "inboxrules: unknown rule key");
1075                 }
1076
1077                 // If the rule involves a field comparison, load the field to be compared.
1078                 compare_me[0] = 0;
1079                 switch(ii->rules[i].compared_field) {
1080
1081                         case field_from:                // From:
1082                                 if (!IsEmptyStr(msg->cm_fields[erFc822Addr])) {
1083                                         safestrncpy(compare_me, msg->cm_fields[erFc822Addr], sizeof compare_me);
1084                                 }
1085                                 break;
1086                         case field_tocc:                // To: or Cc:
1087                                 if (!IsEmptyStr(msg->cm_fields[eRecipient])) {
1088                                         safestrncpy(compare_me, msg->cm_fields[eRecipient], sizeof compare_me);
1089                                 }
1090                                 if (!IsEmptyStr(msg->cm_fields[eCarbonCopY])) {
1091                                         if (!IsEmptyStr(compare_me)) {
1092                                                 strcat(compare_me, ",");
1093                                         }
1094                                         safestrncpy(&compare_me[strlen(compare_me)], msg->cm_fields[eCarbonCopY], (sizeof compare_me - strlen(compare_me)));
1095                                 }
1096                                 break;
1097                         case field_subject:             // Subject:
1098                                 if (!IsEmptyStr(msg->cm_fields[eMsgSubject])) {
1099                                         safestrncpy(compare_me, msg->cm_fields[eMsgSubject], sizeof compare_me);
1100                                 }
1101                                 break;
1102                         case field_replyto:             // Reply-to:
1103                                 if (!IsEmptyStr(msg->cm_fields[eReplyTo])) {
1104                                         safestrncpy(compare_me, msg->cm_fields[eReplyTo], sizeof compare_me);
1105                                 }
1106                                 break;
1107                         case field_listid:              // List-ID:
1108                                 if (!IsEmptyStr(msg->cm_fields[eListID])) {
1109                                         safestrncpy(compare_me, msg->cm_fields[eListID], sizeof compare_me);
1110                                 }
1111                                 break;
1112                         case field_envto:               // Envelope-to:
1113                                 if (!IsEmptyStr(msg->cm_fields[eenVelopeTo])) {
1114                                         safestrncpy(compare_me, msg->cm_fields[eenVelopeTo], sizeof compare_me);
1115                                 }
1116                                 break;
1117                         case field_envfrom:             // Return-path:
1118                                 if (!IsEmptyStr(msg->cm_fields[eMessagePath])) {
1119                                         safestrncpy(compare_me, msg->cm_fields[eMessagePath], sizeof compare_me);
1120                                 }
1121                                 break;
1122
1123                         case field_sender:
1124                         case field_resentfrom:
1125                         case field_resentto:
1126                         case field_xmailer:
1127                         case field_xspamflag:
1128                         case field_xspamstatus:
1129
1130                         default:
1131                                 break;
1132                 }
1133
1134                 // Message data to compare is loaded, now do something.
1135                 switch(ii->rules[i].compared_field) {
1136                         case field_from:                // From:
1137                         case field_tocc:                // To: or Cc:
1138                         case field_subject:             // Subject:
1139                         case field_replyto:             // Reply-to:
1140                         case field_listid:              // List-ID:
1141                         case field_envto:               // Envelope-to:
1142                         case field_envfrom:             // Return-path:
1143                         case field_sender:
1144                         case field_resentfrom:
1145                         case field_resentto:
1146                         case field_xmailer:
1147                         case field_xspamflag:
1148                         case field_xspamstatus:
1149
1150                                 // For all of the above fields, we can compare the field we've loaded into the buffer.
1151                                 syslog(LOG_DEBUG, "Value of field to compare is: <%s>", compare_me);
1152                                 switch(ii->rules[i].field_compare_op) {
1153                                         case fcomp_contains:
1154                                         case fcomp_matches:
1155                                                 rule_activated = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 1 : 0);
1156                                                 syslog(LOG_DEBUG, "Does %s contain %s? %s", compare_me, ii->rules[i].compared_value, rule_activated?"yes":"no");
1157                                                 break;
1158                                         case fcomp_notcontains:
1159                                         case fcomp_notmatches:
1160                                                 rule_activated = (bmstrcasestr(compare_me, ii->rules[i].compared_value) ? 0 : 1);
1161                                                 syslog(LOG_DEBUG, "Does %s contain %s? %s", compare_me, ii->rules[i].compared_value, rule_activated?"yes":"no");
1162                                                 break;
1163                                         case fcomp_is:
1164                                                 rule_activated = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 0 : 1);
1165                                                 syslog(LOG_DEBUG, "Does %s equal %s? %s", compare_me, ii->rules[i].compared_value, rule_activated?"yes":"no");
1166                                                 break;
1167                                         case fcomp_isnot:
1168                                                 rule_activated = (strcasecmp(compare_me, ii->rules[i].compared_value) ? 1 : 0);
1169                                                 syslog(LOG_DEBUG, "Does %s equal %s? %s", compare_me, ii->rules[i].compared_value, rule_activated?"yes":"no");
1170                                                 break;
1171                                 }
1172                                 break;
1173
1174                         case field_size:
1175                                 rule_activated = 0;
1176                                 syslog(LOG_DEBUG, "comparing actual message size %ld to rule message size %ld", smi.meta_rfc822_length, ii->rules[i].compared_size);
1177                                 switch(ii->rules[i].field_compare_op) {
1178                                         case scomp_larger:
1179                                                 rule_activated = ((smi.meta_rfc822_length > ii->rules[i].compared_size) ? 1 : 0);
1180                                                 syslog(LOG_DEBUG, "Is %ld larger than %ld? %s", smi.meta_rfc822_length, ii->rules[i].compared_size, (smi.meta_rfc822_length > ii->rules[i].compared_size) ? "yes":"no");
1181                                                 break;
1182                                         case scomp_smaller:
1183                                                 rule_activated = ((smi.meta_rfc822_length < ii->rules[i].compared_size) ? 1 : 0);
1184                                                 syslog(LOG_DEBUG, "Is %ld smaller than %ld? %s", smi.meta_rfc822_length, ii->rules[i].compared_size, (smi.meta_rfc822_length < ii->rules[i].compared_size) ? "yes":"no");
1185                                                 break;
1186                                 }
1187                                 break;
1188                         case field_all:                 // The "all messages" rule ALWAYS triggers
1189                                 rule_activated = 1;
1190                                 break;
1191                         default:                        // no matches, fall through and do nothing
1192                                 syslog(LOG_DEBUG, "inboxrules: an unknown field comparison was encountered");
1193                                 rule_activated = 0;
1194                                 break;
1195                 }
1196
1197                 // FIXME you are here YOU ARE HERE next write the code to take action
1198                 if (rule_activated) {
1199                         syslog(LOG_DEBUG, "\033[32m\033[7mrule activated\033[0m");              // FIXME remove color
1200                 }
1201                 else {
1202                         syslog(LOG_DEBUG, "\033[31m\033[7mrule not activated\033[0m");          // FIXME remove color
1203                 }
1204         
1205         }
1206
1207         if (msg != NULL) {
1208                 CM_Free(msg);
1209         }
1210
1211
1212 /*
1213  * A user account is identified as requring inbox processing.
1214  * Do it.
1215  */
1216 void do_inbox_processing_for_user(long usernum) {
1217         struct CtdlMessage *msg;
1218         struct inboxrules *ii;
1219         char roomname[ROOMNAMELEN];
1220
1221         if (CtdlGetUserByNumber(&CC->user, usernum) == 0) {
1222                 if (CC->user.msgnum_inboxrules <= 0) {
1223                         return;                                         // this user has no inbox rules
1224                 }
1225
1226                 msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
1227                 if (msg == NULL) {
1228                         return;                                         // config msgnum is set but that message does not exist
1229                 }
1230         
1231                 ii = deserialize_inbox_rules(msg->cm_fields[eMesageText]);
1232                 CM_Free(msg);
1233         
1234                 if (ii == NULL) {
1235                         return;                                         // config message exists but body is null
1236                 }
1237
1238                 syslog(LOG_DEBUG, "inboxrules: for %s", CC->user.fullname);
1239
1240                 // do something now FIXME actually write this
1241
1242                 snprintf(roomname, sizeof roomname, "%010ld.%s", usernum, MAILROOM);
1243                 if (CtdlGetRoom(&CC->room, roomname) == 0) {
1244                         syslog(LOG_DEBUG, "GOT DA ROOM!  WE R000000000L!");
1245
1246                                 /* Do something useful */
1247                                 CtdlForEachMessage(MSGS_GT, ii->lastproc, NULL, NULL, NULL, inbox_do_msg, (void *) ii);
1248
1249                 }
1250
1251                 // FIXME reserialize our inbox rules/state and write changes back to the config room
1252                 free_inbox_rules(ii);
1253         }
1254 }
1255
1256
1257 /*
1258  * Here is an array of users (by number) who have received messages in their inbox and may require processing.
1259  */
1260 long *users_requiring_inbox_processing = NULL;
1261 int num_urip = 0;
1262 int num_urip_alloc = 0;
1263
1264
1265 /*
1266  * Perform inbox processing for all rooms which require it
1267  */
1268 void perform_inbox_processing(void) {
1269         if (num_urip == 0) {
1270                 return;                                                                                 // no action required
1271         }
1272
1273         for (int i=0; i<num_urip; ++i) {
1274                 do_inbox_processing_for_user(users_requiring_inbox_processing[i]);
1275         }
1276
1277         free(users_requiring_inbox_processing);
1278         users_requiring_inbox_processing = NULL;
1279         num_urip = 0;
1280         num_urip_alloc = 0;
1281 }
1282
1283
1284 /*
1285  * This function is called after a message is saved to a room.
1286  * If it's someone's inbox, we have to check for inbox rules
1287  */
1288 int serv_inboxrules_roomhook(struct ctdlroom *room) {
1289
1290         // Is this someone's inbox?
1291         if (!strcasecmp(&room->QRname[11], MAILROOM)) {
1292                 long usernum = atol(room->QRname);
1293                 if (usernum > 0) {
1294
1295                         // first check to see if this user is already on the list
1296                         if (num_urip > 0) {
1297                                 for (int i=0; i<=num_urip; ++i) {
1298                                         if (users_requiring_inbox_processing[i] == usernum) {           // already on the list!
1299                                                 return(0);
1300                                         }
1301                                 }
1302                         }
1303
1304                         // make room if we need to
1305                         if (num_urip_alloc == 0) {
1306                                 num_urip_alloc = 100;
1307                                 users_requiring_inbox_processing = malloc(sizeof(long) * num_urip_alloc);
1308                         }
1309                         else if (num_urip >= num_urip_alloc) {
1310                                 num_urip_alloc += 100;
1311                                 users_requiring_inbox_processing = realloc(users_requiring_inbox_processing, (sizeof(long) * num_urip_alloc));
1312                         }
1313                         
1314                         // now add the user to the list
1315                         users_requiring_inbox_processing[num_urip++] = usernum;
1316                 }
1317         }
1318
1319         // No errors are possible from this function.
1320         return(0);
1321 }
1322
1323
1324
1325 /*
1326  * Get InBox Rules
1327  *
1328  * This is a client-facing function which fetches the user's inbox rules -- it omits all lines containing anything other than a rule.
1329  * 
1330  * hmmmmm ... should we try to rebuild this in terms of deserialize_inbox_rules() instread?
1331  */
1332 void cmd_gibr(char *argbuf) {
1333
1334         if (CtdlAccessCheck(ac_logged_in)) return;
1335
1336         cprintf("%d inbox rules for %s\n", LISTING_FOLLOWS, CC->user.fullname);
1337
1338         struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
1339         if (msg != NULL) {
1340                 if (!CM_IsEmpty(msg, eMesageText)) {
1341                         char *token; 
1342                         char *rest = msg->cm_fields[eMesageText];
1343                         while ((token = strtok_r(rest, "\n", &rest))) {
1344
1345                                 // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule" 
1346                                 if (!strncasecmp(token, "# WEBCIT_RULE|", 14)) {
1347                                         strcpy(token, "rule|"); 
1348                                         strcpy(&token[5], &token[14]);
1349                                 }
1350
1351                                 // Output only lines containing rules.
1352                                 if (!strncasecmp(token, "rule|", 5)) {
1353                                         cprintf("%s\n", token); 
1354                                 }
1355                         }
1356                 }
1357                 CM_Free(msg);
1358         }
1359         cprintf("000\n");
1360 }
1361
1362
1363 /*
1364  * Put InBox Rules
1365  *
1366  * User transmits the new inbox rules for the account.  They are inserted into the account, replacing the ones already there.
1367  */
1368 void cmd_pibr(char *argbuf) {
1369         if (CtdlAccessCheck(ac_logged_in)) return;
1370
1371         unbuffer_output();
1372         cprintf("%d send new rules\n", SEND_LISTING);
1373         char *newrules = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
1374         StrBuf *NewConfig = NewStrBufPlain("Content-type: application/x-citadel-sieve-config; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
1375
1376         char *token; 
1377         char *rest = newrules;
1378         while ((token = strtok_r(rest, "\n", &rest))) {
1379                 // Accept only lines containing rules
1380                 if (!strncasecmp(token, "rule|", 5)) {
1381                         StrBufAppendBufPlain(NewConfig, token, -1, 0);
1382                         StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
1383                 }
1384         }
1385         free(newrules);
1386
1387         // Fetch the existing config so we can merge in anything that is NOT a rule 
1388         // (Does not start with "rule|" but has at least one vertical bar)
1389         struct CtdlMessage *msg = CtdlFetchMessage(CC->user.msgnum_inboxrules, 1);
1390         if (msg != NULL) {
1391                 if (!CM_IsEmpty(msg, eMesageText)) {
1392                         rest = msg->cm_fields[eMesageText];
1393                         while ((token = strtok_r(rest, "\n", &rest))) {
1394                                 // for backwards compatibility, "# WEBCIT_RULE" is an alias for "rule" 
1395                                 if ((strncasecmp(token, "# WEBCIT_RULE|", 14)) && (strncasecmp(token, "rule|", 5)) && (haschar(token, '|'))) {
1396                                         StrBufAppendBufPlain(NewConfig, token, -1, 0);
1397                                         StrBufAppendBufPlain(NewConfig, HKEY("\n"), 0);
1398                                 }
1399                         }
1400                 }
1401                 CM_Free(msg);
1402         }
1403
1404         /* we have composed the new configuration , now save it */
1405         long old_msgnum = CC->user.msgnum_inboxrules;
1406         char userconfigroomname[ROOMNAMELEN];
1407         CtdlMailboxName(userconfigroomname, sizeof userconfigroomname, &CC->user, USERCONFIGROOM);
1408         long new_msgnum = quickie_message("Citadel", NULL, NULL, userconfigroomname, ChrPtr(NewConfig), FMT_RFC822, "inbox rules configuration");
1409         FreeStrBuf(&NewConfig);
1410         CtdlGetUserLock(&CC->user, CC->curr_user);
1411         CC->user.msgnum_inboxrules = new_msgnum;
1412         CtdlPutUserLock(&CC->user);
1413         if (old_msgnum > 0) {
1414                 syslog(LOG_DEBUG, "Deleting old message %ld from %s", old_msgnum, userconfigroomname);
1415                 CtdlDeleteMessages(userconfigroomname, &old_msgnum, 1, "");
1416         }
1417 }
1418
1419
1420 CTDL_MODULE_INIT(sieve)
1421 {
1422         if (!threading)
1423         {
1424                 CtdlRegisterProtoHook(cmd_gibr, "GIBR", "Get InBox Rules");
1425                 CtdlRegisterProtoHook(cmd_pibr, "PIBR", "Put InBox Rules");
1426                 CtdlRegisterRoomHook(serv_inboxrules_roomhook);
1427                 CtdlRegisterSessionHook(perform_inbox_processing, EVT_HOUSE, PRIO_HOUSE + 10);
1428         }
1429         
1430         /* return our module name for the log */
1431         return "inboxrules";
1432 }