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
);
521 msg
= CtdlFetchMessage(msgnum
, 0);
522 if (msg
== NULL
) return;
525 * Grab the message headers so we can feed them to libSieve.
526 * Use HEADERS_ONLY rather than HEADERS_FAST in order to include second-level headers.
528 CC
->redirect_buffer
= malloc(SIZ
);
529 CC
->redirect_len
= 0;
530 CC
->redirect_alloc
= SIZ
;
531 CtdlOutputPreLoadedMsg(msg
, MT_RFC822
, HEADERS_ONLY
, 0, 1, 0);
532 my
.rfc822headers
= CC
->redirect_buffer
;
533 headers_len
= CC
->redirect_len
;
534 CC
->redirect_buffer
= NULL
;
535 CC
->redirect_len
= 0;
536 CC
->redirect_alloc
= 0;
540 CtdlLogPrintf(CTDL_EMERG
, "userdata got clobbz0red! aaaaaaaaghhh!!!!\n");
545 * libSieve clobbers the stack if it encounters badly formed
546 * headers. Sanitize our headers by stripping nonprintable
549 for (i
=0; i
<headers_len
; ++i
) {
550 if (!isascii(my
.rfc822headers
[i
])) {
551 my
.rfc822headers
[i
] = '_';
555 my
.keep
= 0; /* Set to 1 to declare an *explicit* keep */
556 my
.cancel_implicit_keep
= 0; /* Some actions will cancel the implicit keep */
557 my
.usernum
= atol(CC
->room
.QRname
); /* Keep track of the owner of the room's namespace */
558 my
.msgnum
= msgnum
; /* Keep track of the message number in our local store */
559 my
.u
= u
; /* Hand off a pointer to the rest of this info */
561 /* Keep track of the recipient so we can do handling based on it later */
562 process_rfc822_addr(msg
->cm_fields
['R'], my
.recp_user
, my
.recp_node
, my
.recp_name
);
564 /* Keep track of the sender so we can use it for REJECT and VACATION responses */
565 if (msg
->cm_fields
['F'] != NULL
) {
566 safestrncpy(my
.sender
, msg
->cm_fields
['F'], sizeof my
.sender
);
568 else if ( (msg
->cm_fields
['A'] != NULL
) && (msg
->cm_fields
['N'] != NULL
) ) {
569 snprintf(my
.sender
, sizeof my
.sender
, "%s@%s", msg
->cm_fields
['A'], msg
->cm_fields
['N']);
571 else if (msg
->cm_fields
['A'] != NULL
) {
572 safestrncpy(my
.sender
, msg
->cm_fields
['A'], sizeof my
.sender
);
575 strcpy(my
.sender
, "");
578 /* Keep track of the subject so we can use it for VACATION responses */
579 if (msg
->cm_fields
['U'] != NULL
) {
580 safestrncpy(my
.subject
, msg
->cm_fields
['U'], sizeof my
.subject
);
583 strcpy(my
.subject
, "");
586 /* Keep track of the envelope-from address (use body-from if not found) */
587 if (msg
->cm_fields
['P'] != NULL
) {
588 safestrncpy(my
.envelope_from
, msg
->cm_fields
['P'], sizeof my
.envelope_from
);
589 stripallbut(my
.envelope_from
, '<', '>');
591 else if (msg
->cm_fields
['F'] != NULL
) {
592 safestrncpy(my
.envelope_from
, msg
->cm_fields
['F'], sizeof my
.envelope_from
);
593 stripallbut(my
.envelope_from
, '<', '>');
596 strcpy(my
.envelope_from
, "");
599 len
= strlen(my
.envelope_from
);
600 for (i
=0; i
<len
; ++i
) {
601 if (isspace(my
.envelope_from
[i
])) my
.envelope_from
[i
] = '_';
603 if (haschar(my
.envelope_from
, '@') == 0) {
604 strcat(my
.envelope_from
, "@");
605 strcat(my
.envelope_from
, config
.c_fqdn
);
608 /* Keep track of the envelope-to address (use body-to if not found) */
609 if (msg
->cm_fields
['V'] != NULL
) {
610 safestrncpy(my
.envelope_to
, msg
->cm_fields
['V'], sizeof my
.envelope_to
);
611 stripallbut(my
.envelope_to
, '<', '>');
613 else if (msg
->cm_fields
['R'] != NULL
) {
614 safestrncpy(my
.envelope_to
, msg
->cm_fields
['R'], sizeof my
.envelope_to
);
615 if (msg
->cm_fields
['D'] != NULL
) {
616 strcat(my
.envelope_to
, "@");
617 strcat(my
.envelope_to
, msg
->cm_fields
['D']);
619 stripallbut(my
.envelope_to
, '<', '>');
622 strcpy(my
.envelope_to
, "");
625 len
= strlen(my
.envelope_to
);
626 for (i
=0; i
<len
; ++i
) {
627 if (isspace(my
.envelope_to
[i
])) my
.envelope_to
[i
] = '_';
629 if (haschar(my
.envelope_to
, '@') == 0) {
630 strcat(my
.envelope_to
, "@");
631 strcat(my
.envelope_to
, config
.c_fqdn
);
634 CtdlFreeMessage(msg
);
638 CtdlLogPrintf(CTDL_EMERG
, "userdata got clobbz0red! aaaaaaaaghhh!!!!\n");
642 sieve2_setvalue_string(sieve2_context
, "allheaders", my
.rfc822headers
);
646 CtdlLogPrintf(CTDL_EMERG
, "userdata got clobbz0red! aaaaaaaaghhh!!!!\n");
650 CtdlLogPrintf(CTDL_DEBUG
, "Calling sieve2_execute()\n");
651 res
= sieve2_execute(sieve2_context
, &my
);
652 if (res
!= SIEVE2_OK
) {
653 CtdlLogPrintf(CTDL_CRIT
, "sieve2_execute() returned %d: %s\n", res
, sieve2_errstr(res
));
658 CtdlLogPrintf(CTDL_EMERG
, "userdata got clobbz0red! aaaaaaaaghhh!!!!\n");
662 free(my
.rfc822headers
);
663 my
.rfc822headers
= NULL
;
666 * Delete the message from the inbox unless either we were told not to, or
667 * if no other action was successfully taken.
669 if ( (!my
.keep
) && (my
.cancel_implicit_keep
) ) {
670 CtdlLogPrintf(CTDL_DEBUG
, "keep is 0 -- deleting message from inbox\n");
671 CtdlDeleteMessages(CC
->room
.QRname
, &msgnum
, 1, "");
676 CtdlLogPrintf(CTDL_EMERG
, "userdata got clobbz0red! aaaaaaaaghhh!!!!\n");
680 CtdlLogPrintf(CTDL_DEBUG
, "Completed sieve processing on msg <%ld>\n", msgnum
);
681 u
->lastproc
= msgnum
;
689 * Given the on-disk representation of our Sieve config, load
690 * it into an in-memory data structure.
692 void parse_sieve_config(char *conf
, struct sdm_userdata
*u
) {
696 struct sdm_script
*sptr
;
697 struct sdm_vacation
*vptr
;
700 while (c
= ptr
, ptr
= bmstrcasestr(ptr
, CTDLSIEVECONFIGSEPARATOR
), ptr
!= NULL
) {
702 ptr
+= strlen(CTDLSIEVECONFIGSEPARATOR
);
704 extract_token(keyword
, c
, 0, '|', sizeof keyword
);
706 if (!strcasecmp(keyword
, "lastproc")) {
707 u
->lastproc
= extract_long(c
, 1);
710 else if (!strcasecmp(keyword
, "script")) {
711 sptr
= malloc(sizeof(struct sdm_script
));
712 extract_token(sptr
->script_name
, c
, 1, '|', sizeof sptr
->script_name
);
713 sptr
->script_active
= extract_int(c
, 2);
714 remove_token(c
, 0, '|');
715 remove_token(c
, 0, '|');
716 remove_token(c
, 0, '|');
717 sptr
->script_content
= strdup(c
);
718 sptr
->next
= u
->first_script
;
719 u
->first_script
= sptr
;
722 else if (!strcasecmp(keyword
, "vacation")) {
724 if (c
!= NULL
) while (vacrec
=c
, c
=strchr(c
, '\n'), (c
!= NULL
)) {
729 if (strncasecmp(vacrec
, "vacation|", 9)) {
730 vptr
= malloc(sizeof(struct sdm_vacation
));
731 extract_token(vptr
->fromaddr
, vacrec
, 0, '|', sizeof vptr
->fromaddr
);
732 vptr
->timestamp
= extract_long(vacrec
, 1);
733 vptr
->next
= u
->first_vacation
;
734 u
->first_vacation
= vptr
;
739 /* ignore unknown keywords */
744 * We found the Sieve configuration for this user.
745 * Now do something with it.
747 void get_sieve_config_backend(long msgnum
, void *userdata
) {
748 struct sdm_userdata
*u
= (struct sdm_userdata
*) userdata
;
749 struct CtdlMessage
*msg
;
752 u
->config_msgnum
= msgnum
;
753 msg
= CtdlFetchMessage(msgnum
, 1);
755 u
->config_msgnum
= (-1) ;
759 conf
= msg
->cm_fields
['M'];
760 msg
->cm_fields
['M'] = NULL
;
761 CtdlFreeMessage(msg
);
764 parse_sieve_config(conf
, u
);
772 * Write our citadel sieve config back to disk
774 * (Set yes_write_to_disk to nonzero to make it actually write the config;
775 * otherwise it just frees the data structures.)
777 void rewrite_ctdl_sieve_config(struct sdm_userdata
*u
, int yes_write_to_disk
) {
779 struct sdm_script
*sptr
;
780 struct sdm_vacation
*vptr
;
786 "Content-type: application/x-citadel-sieve-config\n"
788 CTDLSIEVECONFIGSEPARATOR
790 CTDLSIEVECONFIGSEPARATOR
795 while (u
->first_script
!= NULL
) {
798 tsize
= tlen
+ strlen(u
->first_script
->script_content
) +256;
799 text
= realloc(text
, tsize
);
800 sprintf(&text
[strlen(text
)], "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR
,
801 u
->first_script
->script_name
,
802 u
->first_script
->script_active
,
803 u
->first_script
->script_content
805 sptr
= u
->first_script
;
806 u
->first_script
= u
->first_script
->next
;
807 free(sptr
->script_content
);
811 if (u
->first_vacation
!= NULL
) {
813 tsize
= strlen(text
) + 256;
814 for (vptr
= u
->first_vacation
; vptr
!= NULL
; vptr
= vptr
->next
) {
815 tsize
+= strlen(vptr
->fromaddr
+ 32);
817 text
= realloc(text
, tsize
);
819 sprintf(&text
[strlen(text
)], "vacation|\n");
820 while (u
->first_vacation
!= NULL
) {
821 if ( (time(NULL
) - u
->first_vacation
->timestamp
) < (MAX_VACATION
* 86400)) {
822 sprintf(&text
[strlen(text
)], "%s|%ld\n",
823 u
->first_vacation
->fromaddr
,
824 u
->first_vacation
->timestamp
827 vptr
= u
->first_vacation
;
828 u
->first_vacation
= u
->first_vacation
->next
;
831 sprintf(&text
[strlen(text
)], CTDLSIEVECONFIGSEPARATOR
);
834 if (yes_write_to_disk
)
836 /* Save the config */
837 quickie_message("Citadel", NULL
, NULL
, u
->config_roomname
,
840 "Sieve configuration"
843 /* And delete the old one */
844 if (u
->config_msgnum
> 0) {
845 CtdlDeleteMessages(u
->config_roomname
, &u
->config_msgnum
, 1, "");
855 * This is our callback registration table for libSieve.
857 sieve2_callback_t ctdl_sieve_callbacks
[] = {
858 { SIEVE2_ACTION_REJECT
, ctdl_reject
},
859 { SIEVE2_ACTION_VACATION
, ctdl_vacation
},
860 { SIEVE2_ERRCALL_PARSE
, ctdl_errparse
},
861 { SIEVE2_ERRCALL_RUNTIME
, ctdl_errexec
},
862 { SIEVE2_ACTION_FILEINTO
, ctdl_fileinto
},
863 { SIEVE2_ACTION_REDIRECT
, ctdl_redirect
},
864 { SIEVE2_ACTION_DISCARD
, ctdl_discard
},
865 { SIEVE2_ACTION_KEEP
, ctdl_keep
},
866 { SIEVE2_SCRIPT_GETSCRIPT
, ctdl_getscript
},
867 { SIEVE2_DEBUG_TRACE
, ctdl_debug
},
868 { SIEVE2_MESSAGE_GETALLHEADERS
, ctdl_getheaders
},
869 { SIEVE2_MESSAGE_GETSIZE
, ctdl_getsize
},
870 { SIEVE2_MESSAGE_GETENVELOPE
, ctdl_getenvelope
},
872 * These actions are unsupported by Citadel so we don't declare them.
874 { SIEVE2_ACTION_NOTIFY, ctdl_notify },
875 { SIEVE2_MESSAGE_GETSUBADDRESS, ctdl_getsubaddress },
876 { SIEVE2_MESSAGE_GETBODY, ctdl_getbody },
884 * Perform sieve processing for a single room
886 void sieve_do_room(char *roomname
) {
888 struct sdm_userdata u
;
889 sieve2_context_t
*sieve2_context
= NULL
; /* Context for sieve parser */
890 int res
; /* Return code from libsieve calls */
891 long orig_lastproc
= 0;
893 memset(&u
, 0, sizeof u
);
895 /* See if the user who owns this 'mailbox' has any Sieve scripts that
898 snprintf(u
.config_roomname
, sizeof u
.config_roomname
, "%010ld.%s", atol(roomname
), USERCONFIGROOM
);
899 if (getroom(&CC
->room
, u
.config_roomname
) != 0) {
900 CtdlLogPrintf(CTDL_DEBUG
, "<%s> does not exist. No processing is required.\n", u
.config_roomname
);
905 * Find the sieve scripts and control record and do something
907 u
.config_msgnum
= (-1);
908 CtdlForEachMessage(MSGS_LAST
, 1, NULL
, SIEVECONFIG
, NULL
,
909 get_sieve_config_backend
, (void *)&u
);
911 if (u
.config_msgnum
< 0) {
912 CtdlLogPrintf(CTDL_DEBUG
, "No Sieve rules exist. No processing is required.\n");
916 CtdlLogPrintf(CTDL_DEBUG
, "Rules found. Performing Sieve processing for <%s>\n", roomname
);
918 if (getroom(&CC
->room
, roomname
) != 0) {
919 CtdlLogPrintf(CTDL_CRIT
, "ERROR: cannot load <%s>\n", roomname
);
923 /* Initialize the Sieve parser */
925 res
= sieve2_alloc(&sieve2_context
);
926 if (res
!= SIEVE2_OK
) {
927 CtdlLogPrintf(CTDL_CRIT
, "sieve2_alloc() returned %d: %s\n", res
, sieve2_errstr(res
));
931 res
= sieve2_callbacks(sieve2_context
, ctdl_sieve_callbacks
);
932 if (res
!= SIEVE2_OK
) {
933 CtdlLogPrintf(CTDL_CRIT
, "sieve2_callbacks() returned %d: %s\n", res
, sieve2_errstr(res
));
937 /* Validate the script */
939 struct ctdl_sieve my
; /* dummy ctdl_sieve struct just to pass "u" slong */
940 memset(&my
, 0, sizeof my
);
942 res
= sieve2_validate(sieve2_context
, &my
);
943 if (res
!= SIEVE2_OK
) {
944 CtdlLogPrintf(CTDL_CRIT
, "sieve2_validate() returned %d: %s\n", res
, sieve2_errstr(res
));
948 /* Do something useful */
949 u
.sieve2_context
= sieve2_context
;
950 orig_lastproc
= u
.lastproc
;
951 CtdlForEachMessage(MSGS_GT
, u
.lastproc
, NULL
, NULL
, NULL
,
957 res
= sieve2_free(&sieve2_context
);
958 if (res
!= SIEVE2_OK
) {
959 CtdlLogPrintf(CTDL_CRIT
, "sieve2_free() returned %d: %s\n", res
, sieve2_errstr(res
));
962 /* Rewrite the config if we have to */
963 rewrite_ctdl_sieve_config(&u
, (u
.lastproc
> orig_lastproc
) ) ;
968 * Perform sieve processing for all rooms which require it
970 void perform_sieve_processing(void) {
971 struct RoomProcList
*ptr
= NULL
;
973 if (sieve_list
!= NULL
) {
974 CtdlLogPrintf(CTDL_DEBUG
, "Begin Sieve processing\n");
975 while (sieve_list
!= NULL
) {
976 char spoolroomname
[ROOMNAMELEN
];
977 safestrncpy(spoolroomname
, sieve_list
->name
, sizeof spoolroomname
);
978 begin_critical_section(S_SIEVELIST
);
980 /* pop this record off the list */
982 sieve_list
= sieve_list
->next
;
985 /* invalidate any duplicate entries to prevent double processing */
986 for (ptr
=sieve_list
; ptr
!=NULL
; ptr
=ptr
->next
) {
987 if (!strcasecmp(ptr
->name
, spoolroomname
)) {
992 end_critical_section(S_SIEVELIST
);
993 if (spoolroomname
[0] != 0) {
994 sieve_do_room(spoolroomname
);
1001 void msiv_load(struct sdm_userdata
*u
) {
1002 char hold_rm
[ROOMNAMELEN
];
1004 strcpy(hold_rm
, CC
->room
.QRname
); /* save current room */
1006 /* Take a spin through the user's personal address book */
1007 if (getroom(&CC
->room
, USERCONFIGROOM
) == 0) {
1009 u
->config_msgnum
= (-1);
1010 strcpy(u
->config_roomname
, CC
->room
.QRname
);
1011 CtdlForEachMessage(MSGS_LAST
, 1, NULL
, SIEVECONFIG
, NULL
,
1012 get_sieve_config_backend
, (void *)u
);
1016 if (strcmp(CC
->room
.QRname
, hold_rm
)) {
1017 getroom(&CC
->room
, hold_rm
); /* return to saved room */
1021 void msiv_store(struct sdm_userdata
*u
, int yes_write_to_disk
) {
1023 * Initialise the sieve configs last processed message number.
1024 * We don't need to get the highest message number for the users inbox since the systems
1025 * highest message number will be higher than that and loer than this scripts message number
1026 * This prevents this new script from processing any old messages in the inbox.
1027 * Most importantly it will prevent vacation messages being sent to lots of old messages
1030 u
->lastproc
= CtdlGetCurrentMessageNumber();
1031 rewrite_ctdl_sieve_config(u
, yes_write_to_disk
);
1036 * Select the active script.
1037 * (Set script_name to an empty string to disable all scripts)
1039 * Returns 0 on success or nonzero for error.
1041 int msiv_setactive(struct sdm_userdata
*u
, char *script_name
) {
1043 struct sdm_script
*s
;
1045 /* First see if the supplied value is ok */
1047 if (IsEmptyStr(script_name
)) {
1051 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1052 if (!strcasecmp(s
->script_name
, script_name
)) {
1058 if (!ok
) return(-1);
1060 /* Now set the active script */
1061 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1062 if (!strcasecmp(s
->script_name
, script_name
)) {
1063 s
->script_active
= 1;
1066 s
->script_active
= 0;
1075 * Fetch a script by name.
1077 * Returns NULL if the named script was not found, or a pointer to the script
1078 * if it was found. NOTE: the caller does *not* own the memory returned by
1079 * this function. Copy it if you need to keep it.
1081 char *msiv_getscript(struct sdm_userdata
*u
, char *script_name
) {
1082 struct sdm_script
*s
;
1084 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1085 if (!strcasecmp(s
->script_name
, script_name
)) {
1086 if (s
->script_content
!= NULL
) {
1087 return (s
->script_content
);
1097 * Delete a script by name.
1099 * Returns 0 if the script was deleted.
1100 * 1 if the script was not found.
1101 * 2 if the script cannot be deleted because it is active.
1103 int msiv_deletescript(struct sdm_userdata
*u
, char *script_name
) {
1104 struct sdm_script
*s
= NULL
;
1105 struct sdm_script
*script_to_delete
= NULL
;
1107 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1108 if (!strcasecmp(s
->script_name
, script_name
)) {
1109 script_to_delete
= s
;
1110 if (s
->script_active
) {
1116 if (script_to_delete
== NULL
) return(1);
1118 if (u
->first_script
== script_to_delete
) {
1119 u
->first_script
= u
->first_script
->next
;
1121 else for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1122 if (s
->next
== script_to_delete
) {
1123 s
->next
= s
->next
->next
;
1127 free(script_to_delete
->script_content
);
1128 free(script_to_delete
);
1134 * Add or replace a new script.
1135 * NOTE: after this function returns, "u" owns the memory that "script_content"
1138 void msiv_putscript(struct sdm_userdata
*u
, char *script_name
, char *script_content
) {
1140 struct sdm_script
*s
, *sptr
;
1142 for (s
=u
->first_script
; s
!=NULL
; s
=s
->next
) {
1143 if (!strcasecmp(s
->script_name
, script_name
)) {
1144 if (s
->script_content
!= NULL
) {
1145 free(s
->script_content
);
1147 s
->script_content
= script_content
;
1152 if (replaced
== 0) {
1153 sptr
= malloc(sizeof(struct sdm_script
));
1154 safestrncpy(sptr
->script_name
, script_name
, sizeof sptr
->script_name
);
1155 sptr
->script_content
= script_content
;
1156 sptr
->script_active
= 0;
1157 sptr
->next
= u
->first_script
;
1158 u
->first_script
= sptr
;
1165 * Citadel protocol to manage sieve scripts.
1166 * This is basically a simplified (read: doesn't resemble IMAP) version
1167 * of the 'managesieve' protocol.
1169 void cmd_msiv(char *argbuf
) {
1171 struct sdm_userdata u
;
1172 char script_name
[256];
1173 char *script_content
= NULL
;
1174 struct sdm_script
*s
;
1176 int changes_made
= 0;
1178 memset(&u
, 0, sizeof(struct sdm_userdata
));
1180 if (CtdlAccessCheck(ac_logged_in
)) return;
1181 extract_token(subcmd
, argbuf
, 0, '|', sizeof subcmd
);
1184 if (!strcasecmp(subcmd
, "putscript")) {
1185 extract_token(script_name
, argbuf
, 1, '|', sizeof script_name
);
1186 if (!IsEmptyStr(script_name
)) {
1187 cprintf("%d Transmit script now\n", SEND_LISTING
);
1188 script_content
= CtdlReadMessageBody("000", config
.c_maxmsglen
, NULL
, 0, 0);
1189 msiv_putscript(&u
, script_name
, script_content
);
1193 cprintf("%d Invalid script name.\n", ERROR
+ ILLEGAL_VALUE
);
1197 else if (!strcasecmp(subcmd
, "listscripts")) {
1198 cprintf("%d Scripts:\n", LISTING_FOLLOWS
);
1199 for (s
=u
.first_script
; s
!=NULL
; s
=s
->next
) {
1200 if (s
->script_content
!= NULL
) {
1201 cprintf("%s|%d|\n", s
->script_name
, s
->script_active
);
1207 else if (!strcasecmp(subcmd
, "setactive")) {
1208 extract_token(script_name
, argbuf
, 1, '|', sizeof script_name
);
1209 if (msiv_setactive(&u
, script_name
) == 0) {
1210 cprintf("%d ok\n", CIT_OK
);
1214 cprintf("%d Script '%s' does not exist.\n",
1215 ERROR
+ ILLEGAL_VALUE
,
1221 else if (!strcasecmp(subcmd
, "getscript")) {
1222 extract_token(script_name
, argbuf
, 1, '|', sizeof script_name
);
1223 script_content
= msiv_getscript(&u
, script_name
);
1224 if (script_content
!= NULL
) {
1227 cprintf("%d Script:\n", LISTING_FOLLOWS
);
1228 script_len
= strlen(script_content
);
1229 client_write(script_content
, script_len
);
1230 if (script_content
[script_len
-1] != '\n') {
1236 cprintf("%d Invalid script name.\n", ERROR
+ ILLEGAL_VALUE
);
1240 else if (!strcasecmp(subcmd
, "deletescript")) {
1241 extract_token(script_name
, argbuf
, 1, '|', sizeof script_name
);
1242 i
= msiv_deletescript(&u
, script_name
);
1244 cprintf("%d ok\n", CIT_OK
);
1248 cprintf("%d Script '%s' does not exist.\n",
1249 ERROR
+ ILLEGAL_VALUE
,
1254 cprintf("%d Script '%s' is active and cannot be deleted.\n",
1255 ERROR
+ ILLEGAL_VALUE
,
1260 cprintf("%d unknown error\n", ERROR
);
1265 cprintf("%d Invalid subcommand\n", ERROR
+ CMD_NOT_SUPPORTED
);
1268 msiv_store(&u
, changes_made
);
1273 void ctdl_sieve_init(void) {
1275 sieve2_context_t
*sieve2_context
= NULL
;
1279 * We don't really care about dumping the entire credits to the log
1280 * every time the server is initialized. The documentation will suffice
1281 * for that purpose. We are making a call to sieve2_credits() in order
1282 * to demonstrate that we have successfully linked in to libsieve.
1284 cred
= strdup(sieve2_credits());
1285 if (cred
== NULL
) return;
1287 if (strlen(cred
) > 60) {
1288 strcpy(&cred
[55], "...");
1291 CtdlLogPrintf(CTDL_INFO
, "%s\n",cred
);
1294 /* Briefly initialize a Sieve parser instance just so we can list the
1295 * extensions that are available.
1297 res
= sieve2_alloc(&sieve2_context
);
1298 if (res
!= SIEVE2_OK
) {
1299 CtdlLogPrintf(CTDL_CRIT
, "sieve2_alloc() returned %d: %s\n", res
, sieve2_errstr(res
));
1303 res
= sieve2_callbacks(sieve2_context
, ctdl_sieve_callbacks
);
1304 if (res
!= SIEVE2_OK
) {
1305 CtdlLogPrintf(CTDL_CRIT
, "sieve2_callbacks() returned %d: %s\n", res
, sieve2_errstr(res
));
1309 msiv_extensions
= strdup(sieve2_listextensions(sieve2_context
));
1310 CtdlLogPrintf(CTDL_INFO
, "Extensions: %s\n", msiv_extensions
);
1312 BAIL
: res
= sieve2_free(&sieve2_context
);
1313 if (res
!= SIEVE2_OK
) {
1314 CtdlLogPrintf(CTDL_CRIT
, "sieve2_free() returned %d: %s\n", res
, sieve2_errstr(res
));
1319 int serv_sieve_room(struct ctdlroom
*room
)
1321 if (!strcasecmp(&room
->QRname
[11], MAILROOM
)) {
1322 sieve_queue_room(room
);
1327 CTDL_MODULE_INIT(sieve
)
1333 CtdlRegisterProtoHook(cmd_msiv
, "MSIV", "Manage Sieve scripts");
1334 CtdlRegisterRoomHook(serv_sieve_room
);
1335 CtdlRegisterSessionHook(perform_sieve_processing
, EVT_HOUSE
);
1338 /* return our Subversion id for the Log */