4 * This module glues libSieve to the Citadel server in order to implement
5 * the Sieve mailbox filtering language (RFC 3028).
7 * This code is released under the terms of the GNU General Public License.
18 #include <sys/types.h>
20 #if TIME_WITH_SYS_TIME
21 # include <sys/time.h>
25 # include <sys/time.h>
34 #include <libcitadel.h>
37 #include "citserver.h"
44 #include "internet_addressing.h"
45 #include "ctdl_module.h"
46 #include "serv_sieve.h"
48 struct RoomProcList
*sieve_list
= NULL
;
49 char *msiv_extensions
= NULL
;
53 * Callback function to send libSieve trace messages to Citadel log facility
55 int ctdl_debug(sieve2_context_t
*s
, void *my
)
57 CtdlLogPrintf(CTDL_DEBUG
, "Sieve: %s\n", sieve2_getvalue_string(s
, "message"));
63 * Callback function to log script parsing errors
65 int ctdl_errparse(sieve2_context_t
*s
, void *my
)
67 CtdlLogPrintf(CTDL_WARNING
, "Error in script, line %d: %s\n",
68 sieve2_getvalue_int(s
, "lineno"),
69 sieve2_getvalue_string(s
, "message")
76 * Callback function to log script execution errors
78 int ctdl_errexec(sieve2_context_t
*s
, void *my
)
80 CtdlLogPrintf(CTDL_WARNING
, "Error executing script: %s\n",
81 sieve2_getvalue_string(s
, "message")
88 * Callback function to redirect a message to a different folder
90 int ctdl_redirect(sieve2_context_t
*s
, void *my
)
92 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
93 struct CtdlMessage
*msg
= NULL
;
94 struct recptypes
*valid
= NULL
;
97 safestrncpy(recp
, sieve2_getvalue_string(s
, "address"), sizeof recp
);
99 CtdlLogPrintf(CTDL_DEBUG
, "Action is REDIRECT, recipient <%s>\n", recp
);
101 valid
= validate_recipients(recp
, NULL
, 0);
103 CtdlLogPrintf(CTDL_WARNING
, "REDIRECT failed: bad recipient <%s>\n", recp
);
104 return SIEVE2_ERROR_BADARGS
;
106 if (valid
->num_error
> 0) {
107 CtdlLogPrintf(CTDL_WARNING
, "REDIRECT failed: bad recipient <%s>\n", recp
);
108 free_recipients(valid
);
109 return SIEVE2_ERROR_BADARGS
;
112 msg
= CtdlFetchMessage(cs
->msgnum
, 1);
114 CtdlLogPrintf(CTDL_WARNING
, "REDIRECT failed: unable to fetch msg %ld\n", cs
->msgnum
);
115 free_recipients(valid
);
116 return SIEVE2_ERROR_BADARGS
;
119 CtdlSubmitMsg(msg
, valid
, NULL
, 0);
120 cs
->cancel_implicit_keep
= 1;
121 free_recipients(valid
);
122 CtdlFreeMessage(msg
);
128 * Callback function to indicate that a message *will* be kept in the inbox
130 int ctdl_keep(sieve2_context_t
*s
, void *my
)
132 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
134 CtdlLogPrintf(CTDL_DEBUG
, "Action is KEEP\n");
137 cs
->cancel_implicit_keep
= 1;
143 * Callback function to file a message into a different mailbox
145 int ctdl_fileinto(sieve2_context_t
*s
, void *my
)
147 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
148 const char *dest_folder
= sieve2_getvalue_string(s
, "mailbox");
150 char foldername
[256];
151 char original_room_name
[ROOMNAMELEN
];
153 CtdlLogPrintf(CTDL_DEBUG
, "Action is FILEINTO, destination is <%s>\n", dest_folder
);
155 /* FILEINTO 'INBOX' is the same thing as KEEP */
156 if ( (!strcasecmp(dest_folder
, "INBOX")) || (!strcasecmp(dest_folder
, MAILROOM
)) ) {
158 cs
->cancel_implicit_keep
= 1;
162 /* Remember what room we came from */
163 safestrncpy(original_room_name
, CC
->room
.QRname
, sizeof original_room_name
);
165 /* First try a mailbox name match (check personal mail folders first) */
166 snprintf(foldername
, sizeof foldername
, "%010ld.%s", cs
->usernum
, dest_folder
);
167 c
= getroom(&CC
->room
, foldername
);
169 /* Then a regular room name match (public and private rooms) */
171 safestrncpy(foldername
, dest_folder
, sizeof foldername
);
172 c
= getroom(&CC
->room
, foldername
);
176 CtdlLogPrintf(CTDL_WARNING
, "FILEINTO failed: target <%s> does not exist\n", dest_folder
);
177 return SIEVE2_ERROR_BADARGS
;
180 /* Yes, we actually have to go there */
181 usergoto(NULL
, 0, 0, NULL
, NULL
);
183 c
= CtdlSaveMsgPointersInRoom(NULL
, &cs
->msgnum
, 1, 0, NULL
);
185 /* Go back to the room we came from */
186 if (strcasecmp(original_room_name
, CC
->room
.QRname
)) {
187 usergoto(original_room_name
, 0, 0, NULL
, NULL
);
191 cs
->cancel_implicit_keep
= 1;
195 return SIEVE2_ERROR_BADARGS
;
201 * Callback function to indicate that a message should be discarded.
203 int ctdl_discard(sieve2_context_t
*s
, void *my
)
205 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
207 CtdlLogPrintf(CTDL_DEBUG
, "Action is DISCARD\n");
209 /* Cancel the implicit keep. That's all there is to it. */
210 cs
->cancel_implicit_keep
= 1;
217 * Callback function to indicate that a message should be rejected
219 int ctdl_reject(sieve2_context_t
*s
, void *my
)
221 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
222 char *reject_text
= NULL
;
224 CtdlLogPrintf(CTDL_DEBUG
, "Action is REJECT\n");
226 /* If we don't know who sent the message, do a DISCARD instead. */
227 if (IsEmptyStr(cs
->sender
)) {
228 CtdlLogPrintf(CTDL_INFO
, "Unknown sender. Doing DISCARD instead of REJECT.\n");
229 return ctdl_discard(s
, my
);
232 /* Assemble the reject message. */
233 reject_text
= malloc(strlen(sieve2_getvalue_string(s
, "message")) + 1024);
234 if (reject_text
== NULL
) {
235 return SIEVE2_ERROR_FAIL
;
239 "Content-type: text/plain\n"
241 "The message was refused by the recipient's mail filtering program.\n"
242 "The reason given was as follows:\n"
247 sieve2_getvalue_string(s
, "message")
250 quickie_message( /* This delivers the message */
257 "Delivery status notification"
261 cs
->cancel_implicit_keep
= 1;
268 * Callback function to indicate that a vacation message should be generated
270 int ctdl_vacation(sieve2_context_t
*s
, void *my
)
272 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
273 struct sdm_vacation
*vptr
;
276 char *vacamsg_text
= NULL
;
277 char vacamsg_subject
[1024];
279 CtdlLogPrintf(CTDL_DEBUG
, "Action is VACATION\n");
281 message
= sieve2_getvalue_string(s
, "message");
282 if (message
== NULL
) return SIEVE2_ERROR_BADARGS
;
284 if (sieve2_getvalue_string(s
, "subject") != NULL
) {
285 safestrncpy(vacamsg_subject
, sieve2_getvalue_string(s
, "subject"), sizeof vacamsg_subject
);
288 snprintf(vacamsg_subject
, sizeof vacamsg_subject
, "Re: %s", cs
->subject
);
291 days
= sieve2_getvalue_int(s
, "days");
292 if (days
< 1) days
= 1;
293 if (days
> MAX_VACATION
) days
= MAX_VACATION
;
295 /* Check to see whether we've already alerted this sender that we're on vacation. */
296 for (vptr
= cs
->u
->first_vacation
; vptr
!= NULL
; vptr
= vptr
->next
) {
297 if (!strcasecmp(vptr
->fromaddr
, cs
->sender
)) {
298 if ( (time(NULL
) - vptr
->timestamp
) < (days
* 86400) ) {
299 CtdlLogPrintf(CTDL_DEBUG
, "Already alerted <%s> recently.\n", cs
->sender
);
305 /* Assemble the reject message. */
306 vacamsg_text
= malloc(strlen(message
) + 1024);
307 if (vacamsg_text
== NULL
) {
308 return SIEVE2_ERROR_FAIL
;
311 sprintf(vacamsg_text
,
312 "Content-type: text/plain\n"
320 quickie_message( /* This delivers the message */
332 /* Now update the list to reflect the fact that we've alerted this sender.
333 * If they're already in the list, just update the timestamp.
335 for (vptr
= cs
->u
->first_vacation
; vptr
!= NULL
; vptr
= vptr
->next
) {
336 if (!strcasecmp(vptr
->fromaddr
, cs
->sender
)) {
337 vptr
->timestamp
= time(NULL
);
342 /* If we get to this point, create a new record.
344 vptr
= malloc(sizeof(struct sdm_vacation
));
345 vptr
->timestamp
= time(NULL
);
346 safestrncpy(vptr
->fromaddr
, cs
->sender
, sizeof vptr
->fromaddr
);
347 vptr
->next
= cs
->u
->first_vacation
;
348 cs
->u
->first_vacation
= vptr
;
355 * Callback function to parse addresses per local system convention
356 * It is disabled because we don't support subaddresses.
359 int ctdl_getsubaddress(sieve2_context_t
*s
, void *my
)
361 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
363 /* libSieve does not take ownership of the memory used here. But, since we
364 * are just pointing to locations inside a struct which we are going to free
367 sieve2_setvalue_string(s
, "user", cs
->recp_user
);
368 sieve2_setvalue_string(s
, "detail", "");
369 sieve2_setvalue_string(s
, "localpart", cs
->recp_user
);
370 sieve2_setvalue_string(s
, "domain", cs
->recp_node
);
377 * Callback function to parse message envelope
379 int ctdl_getenvelope(sieve2_context_t
*s
, void *my
)
381 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
383 CtdlLogPrintf(CTDL_DEBUG
, "Action is GETENVELOPE\nEnvFrom: %s\n EnvTo: %s\n",
384 cs
->envelope_from
, cs
->envelope_to
);
386 if (cs
->envelope_from
!= NULL
) {
387 if ((cs
->envelope_from
[0] != '@')&&(cs
->envelope_from
[strlen(cs
->envelope_from
)-1] != '@')) {
388 sieve2_setvalue_string(s
, "from", cs
->envelope_from
);
391 sieve2_setvalue_string(s
, "from", "invalid_envelope_from@example.org");
395 sieve2_setvalue_string(s
, "from", "null_envelope_from@example.org");
399 if (cs
->envelope_to
!= NULL
) {
400 if ((cs
->envelope_to
[0] != '@') && (cs
->envelope_to
[strlen(cs
->envelope_to
)-1] != '@')) {
401 sieve2_setvalue_string(s
, "to", cs
->envelope_to
);
404 sieve2_setvalue_string(s
, "to", "invalid_envelope_to@example.org");
408 sieve2_setvalue_string(s
, "to", "null_envelope_to@example.org");
416 * Callback function to fetch message body
417 * (Uncomment the code if we implement this extension)
419 int ctdl_getbody(sieve2_context_t *s, void *my)
421 return SIEVE2_ERROR_UNSUPPORTED;
428 * Callback function to fetch message size
430 int ctdl_getsize(sieve2_context_t
*s
, void *my
)
432 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
435 GetMetaData(&smi
, cs
->msgnum
);
437 if (smi
.meta_rfc822_length
> 0L) {
438 sieve2_setvalue_int(s
, "size", (int)smi
.meta_rfc822_length
);
442 return SIEVE2_ERROR_UNSUPPORTED
;
447 * Callback function to retrieve the sieve script
449 int ctdl_getscript(sieve2_context_t
*s
, void *my
) {
450 struct sdm_script
*sptr
;
451 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
453 for (sptr
=cs
->u
->first_script
; sptr
!=NULL
; sptr
=sptr
->next
) {
454 if (sptr
->script_active
> 0) {
455 CtdlLogPrintf(CTDL_DEBUG
, "ctdl_getscript() is using script '%s'\n", sptr
->script_name
);
456 sieve2_setvalue_string(s
, "script", sptr
->script_content
);
461 CtdlLogPrintf(CTDL_DEBUG
, "ctdl_getscript() found no active script\n");
462 return SIEVE2_ERROR_GETSCRIPT
;
466 * Callback function to retrieve message headers
468 int ctdl_getheaders(sieve2_context_t
*s
, void *my
) {
470 struct ctdl_sieve
*cs
= (struct ctdl_sieve
*)my
;
472 CtdlLogPrintf(CTDL_DEBUG
, "ctdl_getheaders() was called\n");
473 sieve2_setvalue_string(s
, "allheaders", cs
->rfc822headers
);
480 * Add a room to the list of those rooms which potentially require sieve processing
482 void sieve_queue_room(struct ctdlroom
*which_room
) {
483 struct RoomProcList
*ptr
;
485 ptr
= (struct RoomProcList
*) malloc(sizeof (struct RoomProcList
));
486 if (ptr
== NULL
) return;
488 safestrncpy(ptr
->name
, which_room
->QRname
, sizeof ptr
->name
);
489 begin_critical_section(S_SIEVELIST
);
490 ptr
->next
= sieve_list
;
492 end_critical_section(S_SIEVELIST
);
493 CtdlLogPrintf(CTDL_DEBUG
, "<%s> queued for Sieve processing\n", which_room
->QRname
);
499 * Perform sieve processing for one message (called by sieve_do_room() for each message)
501 void sieve_do_msg(long msgnum
, void *userdata
) {
502 struct sdm_userdata
*u
= (struct sdm_userdata
*) userdata
;
503 sieve2_context_t
*sieve2_context
;
504 struct ctdl_sieve my
;
506 struct CtdlMessage
*msg
;
508 size_t headers_len
= 0;
513 CtdlLogPrintf(CTDL_EMERG
, "Can't process message <%ld> without userdata!\n", msgnum
);
517 sieve2_context
= u
->sieve2_context
;
519 CtdlLogPrintf(CTDL_DEBUG
, "Performing sieve processing on msg <%ld>\n", msgnum
);
522 * Make sure you include message body so you can get those second-level headers ;)
524 msg
= CtdlFetchMessage(msgnum
, 1);
525 if (msg
== NULL
) return;
528 * Grab the message headers so we can feed them to libSieve.
529 * Use HEADERS_ONLY rather than HEADERS_FAST in order to include second-level headers.
531 CC
->redirect_buffer
= malloc(SIZ
);
532 CC
->redirect_len
= 0;
533 CC
->redirect_alloc
= SIZ
;
534 CtdlOutputPreLoadedMsg(msg
, MT_RFC822
, HEADERS_ONLY
, 0, 1, 0);
535 my
.rfc822headers
= CC
->redirect_buffer
;
536 headers_len
= CC
->redirect_len
;
537 CC
->redirect_buffer
= NULL
;
538 CC
->redirect_len
= 0;
539 CC
->redirect_alloc
= 0;
542 * libSieve clobbers the stack if it encounters badly formed
543 * headers. Sanitize our headers by stripping nonprintable
546 for (i
=0; i
<headers_len
; ++i
) {
547 if (!isascii(my
.rfc822headers
[i
])) {
548 my
.rfc822headers
[i
] = '_';
552 my
.keep
= 0; /* Set to 1 to declare an *explicit* keep */
553 my
.cancel_implicit_keep
= 0; /* Some actions will cancel the implicit keep */
554 my
.usernum
= atol(CC
->room
.QRname
); /* Keep track of the owner of the room's namespace */
555 my
.msgnum
= msgnum
; /* Keep track of the message number in our local store */
556 my
.u
= u
; /* Hand off a pointer to the rest of this info */
558 /* Keep track of the recipient so we can do handling based on it later */
559 process_rfc822_addr(msg
->cm_fields
['R'], my
.recp_user
, my
.recp_node
, my
.recp_name
);
561 /* Keep track of the sender so we can use it for REJECT and VACATION responses */
562 if (msg
->cm_fields
['F'] != NULL
) {
563 safestrncpy(my
.sender
, msg
->cm_fields
['F'], sizeof my
.sender
);
565 else if ( (msg
->cm_fields
['A'] != NULL
) && (msg
->cm_fields
['N'] != NULL
) ) {
566 snprintf(my
.sender
, sizeof my
.sender
, "%s@%s", msg
->cm_fields
['A'], msg
->cm_fields
['N']);
568 else if (msg
->cm_fields
['A'] != NULL
) {
569 safestrncpy(my
.sender
, msg
->cm_fields
['A'], sizeof my
.sender
);
572 strcpy(my
.sender
, "");
575 /* Keep track of the subject so we can use it for VACATION responses */
576 if (msg
->cm_fields
['U'] != NULL
) {
577 safestrncpy(my
.subject
, msg
->cm_fields
['U'], sizeof my
.subject
);
580 strcpy(my
.subject
, "");
583 /* Keep track of the envelope-from address (use body-from if not found) */
584 if (msg
->cm_fields
['P'] != NULL
) {
585 safestrncpy(my
.envelope_from
, msg
->cm_fields
['P'], sizeof my
.envelope_from
);
586 stripallbut(my
.envelope_from
, '<', '>');
588 else if (msg
->cm_fields
['F'] != NULL
) {
589 safestrncpy(my
.envelope_from
, msg
->cm_fields
['F'], sizeof my
.envelope_from
);
590 stripallbut(my
.envelope_from
, '<', '>');
593 strcpy(my
.envelope_from
, "");
596 len
= strlen(my
.envelope_from
);
597 for (i
=0; i
<len
; ++i
) {
598 if (isspace(my
.envelope_from
[i
])) my
.envelope_from
[i
] = '_';
600 if (haschar(my
.envelope_from
, '@') == 0) {
601 strcat(my
.envelope_from
, "@");
602 strcat(my
.envelope_from
, config
.c_fqdn
);
605 /* Keep track of the envelope-to address (use body-to if not found) */
606 if (msg
->cm_fields
['V'] != NULL
) {
607 safestrncpy(my
.envelope_to
, msg
->cm_fields
['V'], sizeof my
.envelope_to
);
608 stripallbut(my
.envelope_to
, '<', '>');
610 else if (msg
->cm_fields
['R'] != NULL
) {
611 safestrncpy(my
.envelope_to
, msg
->cm_fields
['R'], sizeof my
.envelope_to
);
612 if (msg
->cm_fields
['D'] != NULL
) {
613 strcat(my
.envelope_to
, "@");
614 strcat(my
.envelope_to
, msg
->cm_fields
['D']);
616 stripallbut(my
.envelope_to
, '<', '>');
619 strcpy(my
.envelope_to
, "");
622 len
= strlen(my
.envelope_to
);
623 for (i
=0; i
<len
; ++i
) {
624 if (isspace(my
.envelope_to
[i
])) my
.envelope_to
[i
] = '_';
626 if (haschar(my
.envelope_to
, '@') == 0) {
627 strcat(my
.envelope_to
, "@");
628 strcat(my
.envelope_to
, config
.c_fqdn
);
631 CtdlFreeMessage(msg
);
633 sieve2_setvalue_string(sieve2_context
, "allheaders", my
.rfc822headers
);
635 CtdlLogPrintf(CTDL_DEBUG
, "Calling sieve2_execute()\n");
636 res
= sieve2_execute(sieve2_context
, &my
);
637 if (res
!= SIEVE2_OK
) {
638 CtdlLogPrintf(CTDL_CRIT
, "sieve2_execute() returned %d: %s\n", res
, sieve2_errstr(res
));
641 free(my
.rfc822headers
);
642 my
.rfc822headers
= NULL
;
645 * Delete the message from the inbox unless either we were told not to, or
646 * if no other action was successfully taken.
648 if ( (!my
.keep
) && (my
.cancel_implicit_keep
) ) {
649 CtdlLogPrintf(CTDL_DEBUG
, "keep is 0 -- deleting message from inbox\n");
650 CtdlDeleteMessages(CC
->room
.QRname
, &msgnum
, 1, "");
653 CtdlLogPrintf(CTDL_DEBUG
, "Completed sieve processing on msg <%ld>\n", msgnum
);
654 u
->lastproc
= msgnum
;
662 * Given the on-disk representation of our Sieve config, load
663 * it into an in-memory data structure.
665 void parse_sieve_config(char *conf
, struct sdm_userdata
*u
) {
669 struct sdm_script
*sptr
;
670 struct sdm_vacation
*vptr
;
673 while (c
= ptr
, ptr
= bmstrcasestr(ptr
, CTDLSIEVECONFIGSEPARATOR
), ptr
!= NULL
) {
675 ptr
+= strlen(CTDLSIEVECONFIGSEPARATOR
);
677 extract_token(keyword
, c
, 0, '|', sizeof keyword
);
679 if (!strcasecmp(keyword
, "lastproc")) {
680 u
->lastproc
= extract_long(c
, 1);
683 else if (!strcasecmp(keyword
, "script")) {
684 sptr
= malloc(sizeof(struct sdm_script
));
685 extract_token(sptr
->script_name
, c
, 1, '|', sizeof sptr
->script_name
);
686 sptr
->script_active
= extract_int(c
, 2);
687 remove_token(c
, 0, '|');
688 remove_token(c
, 0, '|');
689 remove_token(c
, 0, '|');
690 sptr
->script_content
= strdup(c
);
691 sptr
->next
= u
->first_script
;
692 u
->first_script
= sptr
;
695 else if (!strcasecmp(keyword
, "vacation")) {
697 if (c
!= NULL
) while (vacrec
=c
, c
=strchr(c
, '\n'), (c
!= NULL
)) {
702 if (strncasecmp(vacrec
, "vacation|", 9)) {
703 vptr
= malloc(sizeof(struct sdm_vacation
));
704 extract_token(vptr
->fromaddr
, vacrec
, 0, '|', sizeof vptr
->fromaddr
);
705 vptr
->timestamp
= extract_long(vacrec
, 1);
706 vptr
->next
= u
->first_vacation
;
707 u
->first_vacation
= vptr
;
712 /* ignore unknown keywords */
717 * We found the Sieve configuration for this user.
718 * Now do something with it.
720 void get_sieve_config_backend(long msgnum
, void *userdata
) {
721 struct sdm_userdata
*u
= (struct sdm_userdata
*) userdata
;
722 struct CtdlMessage
*msg
;
725 u
->config_msgnum
= msgnum
;
726 msg
= CtdlFetchMessage(msgnum
, 1);
728 u
->config_msgnum
= (-1) ;
732 conf
= msg
->cm_fields
['M'];
733 msg
->cm_fields
['M'] = NULL
;
734 CtdlFreeMessage(msg
);
737 parse_sieve_config(conf
, u
);
745 * Write our citadel sieve config back to disk
747 * (Set yes_write_to_disk to nonzero to make it actually write the config;
748 * otherwise it just frees the data structures.)
750 void rewrite_ctdl_sieve_config(struct sdm_userdata
*u
, int yes_write_to_disk
) {
752 struct sdm_script
*sptr
;
753 struct sdm_vacation
*vptr
;
759 "Content-type: application/x-citadel-sieve-config\n"
761 CTDLSIEVECONFIGSEPARATOR
763 CTDLSIEVECONFIGSEPARATOR
768 while (u
->first_script
!= NULL
) {
771 tsize
= tlen
+ strlen(u
->first_script
->script_content
) +256;
772 text
= realloc(text
, tsize
);
773 sprintf(&text
[strlen(text
)], "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR
,
774 u
->first_script
->script_name
,
775 u
->first_script
->script_active
,
776 u
->first_script
->script_content
778 sptr
= u
->first_script
;
779 u
->first_script
= u
->first_script
->next
;
780 free(sptr
->script_content
);
784 if (u
->first_vacation
!= NULL
) {
786 tsize
= strlen(text
) + 256;
787 for (vptr
= u
->first_vacation
; vptr
!= NULL
; vptr
= vptr
->next
) {
788 tsize
+= strlen(vptr
->fromaddr
+ 32);
790 text
= realloc(text
, tsize
);
792 sprintf(&text
[strlen(text
)], "vacation|\n");
793 while (u
->first_vacation
!= NULL
) {
794 if ( (time(NULL
) - u
->first_vacation
->timestamp
) < (MAX_VACATION
* 86400)) {
795 sprintf(&text
[strlen(text
)], "%s|%ld\n",
796 u
->first_vacation
->fromaddr
,
797 u
->first_vacation
->timestamp
800 vptr
= u
->first_vacation
;
801 u
->first_vacation
= u
->first_vacation
->next
;
804 sprintf(&text
[strlen(text
)], CTDLSIEVECONFIGSEPARATOR
);
807 if (yes_write_to_disk
)
809 /* Save the config */
810 quickie_message("Citadel", NULL
, NULL
, u
->config_roomname
,
813 "Sieve configuration"
816 /* And delete the old one */
817 if (u
->config_msgnum
> 0) {
818 CtdlDeleteMessages(u
->config_roomname
, &u
->config_msgnum
, 1, "");
828 * This is our callback registration table for libSieve.
830 sieve2_callback_t ctdl_sieve_callbacks
[] = {
831 { SIEVE2_ACTION_REJECT
, ctdl_reject
},
832 { SIEVE2_ACTION_VACATION
, ctdl_vacation
},
833 { SIEVE2_ERRCALL_PARSE
, ctdl_errparse
},
834 { SIEVE2_ERRCALL_RUNTIME
, ctdl_errexec
},
835 { SIEVE2_ACTION_FILEINTO
, ctdl_fileinto
},
836 { SIEVE2_ACTION_REDIRECT
, ctdl_redirect
},
837 { SIEVE2_ACTION_DISCARD
, ctdl_discard
},
838 { SIEVE2_ACTION_KEEP
, ctdl_keep
},
839 { SIEVE2_SCRIPT_GETSCRIPT
, ctdl_getscript
},
840 { SIEVE2_DEBUG_TRACE
, ctdl_debug
},
841 { SIEVE2_MESSAGE_GETALLHEADERS
, ctdl_getheaders
},
842 { SIEVE2_MESSAGE_GETSIZE
, ctdl_getsize
},
843 { SIEVE2_MESSAGE_GETENVELOPE
, ctdl_getenvelope
},
845 * These actions are unsupported by Citadel so we don't declare them.
847 { SIEVE2_ACTION_NOTIFY, ctdl_notify },
848 { SIEVE2_MESSAGE_GETSUBADDRESS, ctdl_getsubaddress },
849 { SIEVE2_MESSAGE_GETBODY, ctdl_getbody },
857 * Perform sieve processing for a single room
859 void sieve_do_room(char *roomname
) {
861 struct sdm_userdata u
;
862 sieve2_context_t
*sieve2_context
= NULL
; /* Context for sieve parser */
863 int res
; /* Return code from libsieve calls */
864 long orig_lastproc
= 0;
866 memset(&u
, 0, sizeof u
);
868 /* See if the user who owns this 'mailbox' has any Sieve scripts that
871 snprintf(u
.config_roomname
, sizeof u
.config_roomname
, "%010ld.%s", atol(roomname
), USERCONFIGROOM
);
872 if (getroom(&CC
->room
, u
.config_roomname
) != 0) {
873 CtdlLogPrintf(CTDL_DEBUG
, "<%s> does not exist. No processing is required.\n", u
.config_roomname
);
878 * Find the sieve scripts and control record and do something
880 u
.config_msgnum
= (-1);
881 CtdlForEachMessage(MSGS_LAST
, 1, NULL
, SIEVECONFIG
, NULL
,
882 get_sieve_config_backend
, (void *)&u
);
884 if (u
.config_msgnum
< 0) {
885 CtdlLogPrintf(CTDL_DEBUG
, "No Sieve rules exist. No processing is required.\n");
889 CtdlLogPrintf(CTDL_DEBUG
, "Rules found. Performing Sieve processing for <%s>\n", roomname
);
891 if (getroom(&CC
->room
, roomname
) != 0) {
892 CtdlLogPrintf(CTDL_CRIT
, "ERROR: cannot load <%s>\n", roomname
);
896 /* Initialize the Sieve parser */
898 res
= sieve2_alloc(&sieve2_context
);
899 if (res
!= SIEVE2_OK
) {
900 CtdlLogPrintf(CTDL_CRIT
, "sieve2_alloc() returned %d: %s\n", res
, sieve2_errstr(res
));
904 res
= sieve2_callbacks(sieve2_context
, ctdl_sieve_callbacks
);
905 if (res
!= SIEVE2_OK
) {
906 CtdlLogPrintf(CTDL_CRIT
, "sieve2_callbacks() returned %d: %s\n", res
, sieve2_errstr(res
));
910 /* Validate the script */
912 struct ctdl_sieve my
; /* dummy ctdl_sieve struct just to pass "u" slong */
913 memset(&my
, 0, sizeof my
);
915 res
= sieve2_validate(sieve2_context
, &my
);
916 if (res
!= SIEVE2_OK
) {
917 CtdlLogPrintf(CTDL_CRIT
, "sieve2_validate() returned %d: %s\n", res
, sieve2_errstr(res
));
921 /* Do something useful */
922 u
.sieve2_context
= sieve2_context
;
923 orig_lastproc
= u
.lastproc
;
924 CtdlForEachMessage(MSGS_GT
, u
.lastproc
, NULL
, NULL
, NULL
,
930 res
= sieve2_free(&sieve2_context
);
931 if (res
!= SIEVE2_OK
) {
932 CtdlLogPrintf(CTDL_CRIT
, "sieve2_free() returned %d: %s\n", res
, sieve2_errstr(res
));
935 /* Rewrite the config if we have to */
936 rewrite_ctdl_sieve_config(&u
, (u
.lastproc
> orig_lastproc
) ) ;
941 * Perform sieve processing for all rooms which require it
943 void perform_sieve_processing(void) {
944 struct RoomProcList
*ptr
= NULL
;
946 if (sieve_list
!= NULL
) {
947 CtdlLogPrintf(CTDL_DEBUG
, "Begin Sieve processing\n");
948 while (sieve_list
!= NULL
) {
949 char spoolroomname
[ROOMNAMELEN
];
950 safestrncpy(spoolroomname
, sieve_list
->name
, sizeof spoolroomname
);
951 begin_critical_section(S_SIEVELIST
);
953 /* pop this record off the list */
955 sieve_list
= sieve_list
->next
;
958 /* invalidate any duplicate entries to prevent double processing */
959 for (ptr
=sieve_list
; ptr
!=NULL
; ptr
=ptr
->next
) {
960 if (!strcasecmp(ptr
->name
, spoolroomname
)) {
965 end_critical_section(S_SIEVELIST
);
966 if (spoolroomname
[0] != 0) {
967 sieve_do_room(spoolroomname
);
974 void msiv_load(struct sdm_userdata
*u
) {
975 char hold_rm
[ROOMNAMELEN
];
977 strcpy(hold_rm
, CC
->room
.QRname
); /* save current room */
979 /* Take a spin through the user's personal address book */
980 if (getroom(&CC
->room
, USERCONFIGROOM
) == 0) {
982 u
->config_msgnum
= (-1);
983 strcpy(u
->config_roomname
, CC
->room
.QRname
);
984 CtdlForEachMessage(MSGS_LAST
, 1, NULL
, SIEVECONFIG
, NULL
,
985 get_sieve_config_backend
, (void *)u
);
989 if (strcmp(CC
->room
.QRname
, hold_rm
)) {
990 getroom(&CC
->room
, hold_rm
); /* return to saved room */
994 void msiv_store(struct sdm_userdata
*u
, int yes_write_to_disk
) {
996 * Initialise the sieve configs last processed message number.
997 * We don't need to get the highest message number for the users inbox since the systems
998 * highest message number will be higher than that and loer than this scripts message number
999 * This prevents this new script from processing any old messages in the inbox.
1000 * Most importantly it will prevent vacation messages being sent to lots of old messages
1003 u
->lastproc
= CtdlGetCurrentMessageNumber();
1004 rewrite_ctdl_sieve_config(u
, yes_write_to_disk
);
1009 * Select the active script.
1010 * (Set script_name to an empty string to disable all scripts)
1012 * Returns 0 on success or nonzero for error.
1014 int msiv_setactive(struct sdm_userdata
*u
, char *script_name
) {
1016 struct sdm_script
*s
;
1018 /* First see if the supplied value is ok */
1020 if (IsEmptyStr(script_name
)) {
1024 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1025 if (!strcasecmp(s
->script_name
, script_name
)) {
1031 if (!ok
) return(-1);
1033 /* Now set the active script */
1034 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1035 if (!strcasecmp(s
->script_name
, script_name
)) {
1036 s
->script_active
= 1;
1039 s
->script_active
= 0;
1048 * Fetch a script by name.
1050 * Returns NULL if the named script was not found, or a pointer to the script
1051 * if it was found. NOTE: the caller does *not* own the memory returned by
1052 * this function. Copy it if you need to keep it.
1054 char *msiv_getscript(struct sdm_userdata
*u
, char *script_name
) {
1055 struct sdm_script
*s
;
1057 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1058 if (!strcasecmp(s
->script_name
, script_name
)) {
1059 if (s
->script_content
!= NULL
) {
1060 return (s
->script_content
);
1070 * Delete a script by name.
1072 * Returns 0 if the script was deleted.
1073 * 1 if the script was not found.
1074 * 2 if the script cannot be deleted because it is active.
1076 int msiv_deletescript(struct sdm_userdata
*u
, char *script_name
) {
1077 struct sdm_script
*s
= NULL
;
1078 struct sdm_script
*script_to_delete
= NULL
;
1080 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1081 if (!strcasecmp(s
->script_name
, script_name
)) {
1082 script_to_delete
= s
;
1083 if (s
->script_active
) {
1089 if (script_to_delete
== NULL
) return(1);
1091 if (u
->first_script
== script_to_delete
) {
1092 u
->first_script
= u
->first_script
->next
;
1094 else for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1095 if (s
->next
== script_to_delete
) {
1096 s
->next
= s
->next
->next
;
1100 free(script_to_delete
->script_content
);
1101 free(script_to_delete
);
1107 * Add or replace a new script.
1108 * NOTE: after this function returns, "u" owns the memory that "script_content"
1111 void msiv_putscript(struct sdm_userdata
*u
, char *script_name
, char *script_content
) {
1113 struct sdm_script
*s
, *sptr
;
1115 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1116 if (!strcasecmp(s
->script_name
, script_name
)) {
1117 if (s
->script_content
!= NULL
) {
1118 free(s
->script_content
);
1120 s
->script_content
= script_content
;
1125 if (replaced
== 0) {
1126 sptr
= malloc(sizeof(struct sdm_script
));
1127 safestrncpy(sptr
->script_name
, script_name
, sizeof sptr
->script_name
);
1128 sptr
->script_content
= script_content
;
1129 sptr
->script_active
= 0;
1130 sptr
->next
= u
->first_script
;
1131 u
->first_script
= sptr
;
1138 * Citadel protocol to manage sieve scripts.
1139 * This is basically a simplified (read: doesn't resemble IMAP) version
1140 * of the 'managesieve' protocol.
1142 void cmd_msiv(char *argbuf
) {
1144 struct sdm_userdata u
;
1145 char script_name
[256];
1146 char *script_content
= NULL
;
1147 struct sdm_script
*s
;
1149 int changes_made
= 0;
1151 memset(&u
, 0, sizeof(struct sdm_userdata
));
1153 if (CtdlAccessCheck(ac_logged_in
)) return;
1154 extract_token(subcmd
, argbuf
, 0, '|', sizeof subcmd
);
1157 if (!strcasecmp(subcmd
, "putscript")) {
1158 extract_token(script_name
, argbuf
, 1, '|', sizeof script_name
);
1159 if (!IsEmptyStr(script_name
)) {
1160 cprintf("%d Transmit script now\n", SEND_LISTING
);
1161 script_content
= CtdlReadMessageBody("000", config
.c_maxmsglen
, NULL
, 0, 0);
1162 msiv_putscript(&u
, script_name
, script_content
);
1166 cprintf("%d Invalid script name.\n", ERROR
+ ILLEGAL_VALUE
);
1170 else if (!strcasecmp(subcmd
, "listscripts")) {
1171 cprintf("%d Scripts:\n", LISTING_FOLLOWS
);
1172 for (s
=u
.first_script
; s
!=NULL
; s
=s
->next
) {
1173 if (s
->script_content
!= NULL
) {
1174 cprintf("%s|%d|\n", s
->script_name
, s
->script_active
);
1180 else if (!strcasecmp(subcmd
, "setactive")) {
1181 extract_token(script_name
, argbuf
, 1, '|', sizeof script_name
);
1182 if (msiv_setactive(&u
, script_name
) == 0) {
1183 cprintf("%d ok\n", CIT_OK
);
1187 cprintf("%d Script '%s' does not exist.\n",
1188 ERROR
+ ILLEGAL_VALUE
,
1194 else if (!strcasecmp(subcmd
, "getscript")) {
1195 extract_token(script_name
, argbuf
, 1, '|', sizeof script_name
);
1196 script_content
= msiv_getscript(&u
, script_name
);
1197 if (script_content
!= NULL
) {
1200 cprintf("%d Script:\n", LISTING_FOLLOWS
);
1201 script_len
= strlen(script_content
);
1202 client_write(script_content
, script_len
);
1203 if (script_content
[script_len
-1] != '\n') {
1209 cprintf("%d Invalid script name.\n", ERROR
+ ILLEGAL_VALUE
);
1213 else if (!strcasecmp(subcmd
, "deletescript")) {
1214 extract_token(script_name
, argbuf
, 1, '|', sizeof script_name
);
1215 i
= msiv_deletescript(&u
, script_name
);
1217 cprintf("%d ok\n", CIT_OK
);
1221 cprintf("%d Script '%s' does not exist.\n",
1222 ERROR
+ ILLEGAL_VALUE
,
1227 cprintf("%d Script '%s' is active and cannot be deleted.\n",
1228 ERROR
+ ILLEGAL_VALUE
,
1233 cprintf("%d unknown error\n", ERROR
);
1238 cprintf("%d Invalid subcommand\n", ERROR
+ CMD_NOT_SUPPORTED
);
1241 msiv_store(&u
, changes_made
);
1246 void ctdl_sieve_init(void) {
1248 sieve2_context_t
*sieve2_context
= NULL
;
1252 * We don't really care about dumping the entire credits to the log
1253 * every time the server is initialized. The documentation will suffice
1254 * for that purpose. We are making a call to sieve2_credits() in order
1255 * to demonstrate that we have successfully linked in to libsieve.
1257 cred
= strdup(sieve2_credits());
1258 if (cred
== NULL
) return;
1260 if (strlen(cred
) > 60) {
1261 strcpy(&cred
[55], "...");
1264 CtdlLogPrintf(CTDL_INFO
, "%s\n",cred
);
1267 /* Briefly initialize a Sieve parser instance just so we can list the
1268 * extensions that are available.
1270 res
= sieve2_alloc(&sieve2_context
);
1271 if (res
!= SIEVE2_OK
) {
1272 CtdlLogPrintf(CTDL_CRIT
, "sieve2_alloc() returned %d: %s\n", res
, sieve2_errstr(res
));
1276 res
= sieve2_callbacks(sieve2_context
, ctdl_sieve_callbacks
);
1277 if (res
!= SIEVE2_OK
) {
1278 CtdlLogPrintf(CTDL_CRIT
, "sieve2_callbacks() returned %d: %s\n", res
, sieve2_errstr(res
));
1282 msiv_extensions
= strdup(sieve2_listextensions(sieve2_context
));
1283 CtdlLogPrintf(CTDL_INFO
, "Extensions: %s\n", msiv_extensions
);
1285 BAIL
: res
= sieve2_free(&sieve2_context
);
1286 if (res
!= SIEVE2_OK
) {
1287 CtdlLogPrintf(CTDL_CRIT
, "sieve2_free() returned %d: %s\n", res
, sieve2_errstr(res
));
1292 int serv_sieve_room(struct ctdlroom
*room
)
1294 if (!strcasecmp(&room
->QRname
[11], MAILROOM
)) {
1295 sieve_queue_room(room
);
1300 CTDL_MODULE_INIT(sieve
)
1306 CtdlRegisterProtoHook(cmd_msiv
, "MSIV", "Manage Sieve scripts");
1307 CtdlRegisterRoomHook(serv_sieve_room
);
1308 CtdlRegisterSessionHook(perform_sieve_processing
, EVT_HOUSE
);
1311 /* return our Subversion id for the Log */