1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
6 Copyright (c) The Exim Maintainers 2016 - 2024
7 Copyright (c) Michael Haardt 2003 - 2015
8 See the file NOTICE for conditions of use and distribution.
9 SPDX-License-Identifier: GPL-2.0-or-later
12 /* This code was contributed by Michael Haardt. */
15 /* Sieve mail filter. */
29 /* Define this for RFC compliant \r\n end-of-line terminators. */
30 /* Undefine it for UNIX-style \n end-of-line terminators (default). */
33 /* Define this for development of the Sieve extension "encoded-character". */
34 #define ENCODED_CHARACTER
36 /* Define this for development of the Sieve extension "envelope-auth". */
39 /* Define this for development of the Sieve extension "enotify". */
42 /* Define this for the Sieve extension "subaddress". */
45 /* Define this for the Sieve extension "vacation". */
49 #define VACATION_MIN_DAYS 1
50 /* Must be >= VACATION_MIN_DAYS, must be > 7, should be > 30 */
51 #define VACATION_MAX_DAYS 31
53 /* Keep this at 75 to accept only RFC compliant MIME words. */
54 /* Increase it if you want to match headers from buggy MUAs. */
55 #define MIMEWORD_LENGTH 75
65 #ifdef ENCODED_CHARACTER
66 BOOL require_encoded_character;
69 int require_envelope_auth;
73 struct Notification *notified;
75 const uschar *enotify_mailto_owner;
77 int require_subaddress;
80 BOOL require_vacation;
84 const uschar *vacation_directory;
85 const uschar *subaddress;
86 const uschar *useraddress;
88 BOOL require_iascii_numeric;
91 enum Comparator { COMP_OCTET, COMP_EN_ASCII_CASEMAP, COMP_ASCII_NUMERIC };
92 enum MatchType { MATCH_IS, MATCH_CONTAINS, MATCH_MATCHES };
94 enum AddressPart { ADDRPART_USER, ADDRPART_DETAIL, ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
96 enum AddressPart { ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
98 enum RelOp { LT, LE, EQ, GE, GT, NE };
100 struct Notification {
104 struct Notification *next;
107 /* This should be a complete list of supported extensions, so that an external
108 ManageSieve (RFC 5804) program can interrogate the current Exim binary for the
109 list of extensions and provide correct information to a client.
111 We'll emit the list in the order given here; keep it alphabetically sorted, so
112 that callers don't get surprised.
114 List *MUST* end with a NULL. Which at least makes ifdef-vs-comma easier. */
116 static const uschar *exim_sieve_extension_list[] = {
117 CUS"comparator-i;ascii-numeric",
119 #ifdef ENCODED_CHARACTER
120 CUS"encoded-character",
139 static int eq_asciicase(const gstring * needle, const gstring * haystack, BOOL match_prefix);
140 static int parse_test(struct Sieve *filter, int *cond, int exec);
141 static int parse_commands(struct Sieve *filter, int exec, address_item **generated);
143 static uschar str_from_c[] = "From";
144 static const gstring str_from = { .s = str_from_c, .ptr = 4, .size = 5 };
145 static uschar str_to_c[] = "To";
146 static const gstring str_to = { .s = str_to_c, .ptr = 2, .size = 3 };
147 static uschar str_cc_c[] = "Cc";
148 static const gstring str_cc = { .s = str_cc_c, .ptr = 2, .size = 3 };
149 static uschar str_bcc_c[] = "Bcc";
150 static const gstring str_bcc = { .s = str_bcc_c, .ptr = 3, .size = 4 };
152 static uschar str_auth_c[] = "auth";
153 static const gstring str_auth = { .s = str_auth_c, .ptr = 4, .size = 5 };
155 static uschar str_sender_c[] = "Sender";
156 static const gstring str_sender = { .s = str_sender_c, .ptr = 6, .size = 7 };
157 static uschar str_resent_from_c[] = "Resent-From";
158 static const gstring str_resent_from = { .s = str_resent_from_c, .ptr = 11, .size = 12 };
159 static uschar str_resent_to_c[] = "Resent-To";
160 static const gstring str_resent_to = { .s = str_resent_to_c, .ptr = 9, .size = 10 };
161 static uschar str_fileinto_c[] = "fileinto";
162 static const gstring str_fileinto = { .s = str_fileinto_c, .ptr = 8, .size = 9 };
163 static uschar str_envelope_c[] = "envelope";
164 static const gstring str_envelope = { .s = str_envelope_c, .ptr = 8, .size = 9 };
165 #ifdef ENCODED_CHARACTER
166 static uschar str_encoded_character_c[] = "encoded-character";
167 static const gstring str_encoded_character = { .s = str_encoded_character_c, .ptr = 17, .size = 18 };
170 static uschar str_envelope_auth_c[] = "envelope-auth";
171 static const gstring str_envelope_auth = { .s = str_envelope_auth_c, .ptr = 13, .size = 14 };
174 static uschar str_enotify_c[] = "enotify";
175 static const gstring str_enotify = { .s = str_enotify_c, .ptr = 7, .size = 8 };
176 static uschar str_online_c[] = "online";
177 static const gstring str_online = { .s = str_online_c, .ptr = 6, .size = 7 };
178 static uschar str_maybe_c[] = "maybe";
179 static const gstring str_maybe = { .s = str_maybe_c, .ptr = 5, .size = 6 };
180 static uschar str_auto_submitted_c[] = "Auto-Submitted";
181 static const gstring str_auto_submitted = { .s = str_auto_submitted_c, .ptr = 14, .size = 15 };
184 static uschar str_subaddress_c[] = "subaddress";
185 static const gstring str_subaddress = { .s = str_subaddress_c, .ptr = 10, .size = 11 };
188 static uschar str_vacation_c[] = "vacation";
189 static const gstring str_vacation = { .s = str_vacation_c, .ptr = 8, .size = 9 };
190 static uschar str_subject_c[] = "Subject";
191 static const gstring str_subject = { .s = str_subject_c, .ptr = 7, .size = 8 };
193 static uschar str_copy_c[] = "copy";
194 static const gstring str_copy = { .s = str_copy_c, .ptr = 4, .size = 5 };
195 static uschar str_iascii_casemap_c[] = "i;ascii-casemap";
196 static const gstring str_iascii_casemap = { .s = str_iascii_casemap_c, .ptr = 15, .size = 16 };
197 static uschar str_enascii_casemap_c[] = "en;ascii-casemap";
198 static const gstring str_enascii_casemap = { .s = str_enascii_casemap_c, .ptr = 16, .size = 17 };
199 static uschar str_ioctet_c[] = "i;octet";
200 static const gstring str_ioctet = { .s = str_ioctet_c, .ptr = 7, .size = 8 };
201 static uschar str_iascii_numeric_c[] = "i;ascii-numeric";
202 static const gstring str_iascii_numeric = { .s = str_iascii_numeric_c, .ptr = 15, .size = 16 };
203 static uschar str_comparator_iascii_casemap_c[] = "comparator-i;ascii-casemap";
204 static const gstring str_comparator_iascii_casemap = { .s = str_comparator_iascii_casemap_c, .ptr = 26, .size = 27 };
205 static uschar str_comparator_enascii_casemap_c[] = "comparator-en;ascii-casemap";
206 static const gstring str_comparator_enascii_casemap = { .s = str_comparator_enascii_casemap_c, .ptr = 27, .size = 28 };
207 static uschar str_comparator_ioctet_c[] = "comparator-i;octet";
208 static const gstring str_comparator_ioctet = { .s = str_comparator_ioctet_c, .ptr = 18, .size = 19 };
209 static uschar str_comparator_iascii_numeric_c[] = "comparator-i;ascii-numeric";
210 static const gstring str_comparator_iascii_numeric = { .s = str_comparator_iascii_numeric_c, .ptr = 26, .size = 27 };
213 /*************************************************
214 * Encode to quoted-printable *
215 *************************************************/
222 dst, allocated, a US-ASCII string
226 quoted_printable_encode(const gstring * src)
228 gstring * dst = NULL;
232 for (const uschar * start = src->s, * end = start + src->ptr;
233 start < end; ++start)
236 if (line >= 73) /* line length limit */
238 dst = string_catn(dst, US"=\n", 2); /* line split */
241 if ( (ch >= '!' && ch <= '<')
242 || (ch >= '>' && ch <= '~')
243 || ( (ch == '\t' || ch == ' ')
244 && start+2 < end && (start[1] != '\r' || start[2] != '\n') /* CRLF */
248 dst = string_catn(dst, start, 1); /* copy char */
251 else if (ch == '\r' && start+1 < end && start[1] == '\n') /* CRLF */
253 dst = string_catn(dst, US"\n", 1); /* NL */
255 ++start; /* consume extra input char */
259 dst = string_fmt_append(dst, "=%02X", ch);
264 (void) string_from_gstring(dst);
265 gstring_release_unused(dst);
270 /*************************************************
271 * Check mail address for correct syntax *
272 *************************************************/
275 Check mail address for being syntactically correct.
278 filter points to the Sieve filter including its state
279 address String containing one address
282 1 Mail address is syntactically OK
287 check_mail_address(struct Sieve * filter, const gstring * address)
289 int start, end, domain;
290 uschar * error, * ss;
292 if (address->ptr > 0)
294 ss = parse_extract_address(address->s, &error, &start, &end, &domain,
298 filter->errmsg = string_sprintf("malformed address \"%s\" (%s)",
307 filter->errmsg = CUS "empty address";
313 /*************************************************
314 * Decode URI encoded string *
315 *************************************************/
319 str URI encoded string
322 str is modified in place
323 TRUE Decoding successful
329 uri_decode(gstring * str)
333 if (str->ptr == 0) return TRUE;
334 for (t = s = str->s, e = s + str->ptr; s < e; )
337 if (s+2 < e && isxdigit(s[1]) && isxdigit(s[2]))
339 *t++ = ((isdigit(s[1]) ? s[1]-'0' : tolower(s[1])-'a'+10)<<4)
340 | (isdigit(s[2]) ? s[2]-'0' : tolower(s[2])-'a'+10);
349 str->ptr = t - str->s;
354 /*************************************************
356 *************************************************/
361 mailtoURI = "mailto:" [ to ] [ headers ]
362 to = [ addr-spec *("%2C" addr-spec ) ]
363 headers = "?" header *( "&" header )
364 header = hname " = " hvalue
369 filter points to the Sieve filter including its state
370 uri URI, excluding scheme
371 recipient list of recipients; prepnded to
375 1 URI is syntactically OK
381 parse_mailto_uri(struct Sieve * filter, const uschar * uri,
382 string_item ** recipient, gstring * header, gstring * subject,
385 const uschar * start;
387 if (Ustrncmp(uri, "mailto:", 7))
389 filter->errmsg = US "Unknown URI scheme";
394 if (*uri && *uri != '?')
398 for (start = uri; *uri && *uri != '?' && (*uri != '%' || uri[1] != '2' || tolower(uri[2]) != 'c'); ++uri);
401 gstring * to = string_catn(NULL, start, uri - start);
406 filter->errmsg = US"Invalid URI encoding";
409 new = store_get(sizeof(string_item), GET_UNTAINTED);
410 new->text = string_from_gstring(to);
411 new->next = *recipient;
416 filter->errmsg = US"Missing addr-spec in URI";
419 if (*uri == '%') uri += 3;
425 gstring * hname = string_get(0), * hvalue = NULL;
428 for (start = uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(), %", *uri)); ++uri) ;
431 hname = string_catn(hname, start, uri-start);
433 if (!uri_decode(hname))
435 filter->errmsg = US"Invalid URI encoding";
442 filter->errmsg = US"Missing equal after hname";
447 for (start = uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(), %", *uri)); ++uri) ;
450 hvalue = string_catn(NULL, start, uri-start); /*XXX this used to say "hname =" */
452 if (!uri_decode(hvalue))
454 filter->errmsg = US"Invalid URI encoding";
458 if (hname->ptr == 2 && strcmpic(hname->s, US"to") == 0)
460 string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
461 new->text = string_from_gstring(hvalue);
462 new->next = *recipient;
465 else if (hname->ptr == 4 && strcmpic(hname->s, US"body") == 0)
467 else if (hname->ptr == 7 && strcmpic(hname->s, US"subject") == 0)
471 static gstring ignore[] =
473 {.s = US"date", .ptr = 4, .size = 5},
474 {.s = US"from", .ptr = 4, .size = 5},
475 {.s = US"message-id", .ptr = 10, .size = 11},
476 {.s = US"received", .ptr = 8, .size = 9},
477 {.s = US"auto-submitted", .ptr = 14, .size = 15}
479 static gstring * end = ignore + nelem(ignore);
482 for (i = ignore; i < end && !eq_asciicase(hname, i, FALSE); ++i);
485 hname = string_fmt_append(NULL, "%Y%Y: %Y\n", header, hname, hvalue);
486 (void) string_from_gstring(hname);
487 /*XXX we seem to do nothing with this new hname? */
490 if (*uri == '&') ++uri;
495 filter->errmsg = US"Syntactically invalid URI";
503 /*************************************************
504 * Octet-wise string comparison *
505 *************************************************/
509 needle UTF-8 string to search ...
510 haystack ... inside the haystack
511 match_prefix TRUE to compare if needle is a prefix of haystack
513 Returns: 0 needle not found in haystack
518 eq_octet(const gstring *needle, const gstring *haystack, BOOL match_prefix)
530 if (*n & 0x80) return 0;
531 if (*h & 0x80) return 0;
533 if (*n != *h) return 0;
539 return (match_prefix ? nl == 0 : nl == 0 && hl == 0);
543 /*************************************************
544 * ASCII case-insensitive string comparison *
545 *************************************************/
549 needle UTF-8 string to search ...
550 haystack ... inside the haystack
551 match_prefix TRUE to compare if needle is a prefix of haystack
553 Returns: 0 needle not found in haystack
558 eq_asciicase(const gstring *needle, const gstring *haystack, BOOL match_prefix)
568 while (nl > 0 && hl > 0)
573 if (nc & 0x80) return 0;
574 if (hc & 0x80) return 0;
576 /* tolower depends on the locale and only ASCII case must be insensitive */
577 if ((nc >= 'A' && nc <= 'Z' ? nc | 0x20 : nc) != (hc >= 'A' && hc <= 'Z' ? hc | 0x20 : hc)) return 0;
583 return (match_prefix ? nl == 0 : nl == 0 && hl == 0);
587 /*************************************************
588 * Glob pattern search *
589 *************************************************/
593 needle pattern to search ...
594 haystack ... inside the haystack
595 ascii_caseless ignore ASCII case
596 match_octet match octets, not UTF-8 multi-octet characters
598 Returns: 0 needle not found in haystack
604 eq_glob(const gstring *needle,
605 const gstring *haystack, BOOL ascii_caseless, BOOL match_octet)
607 const uschar *n, *h, *nend, *hend;
612 nend = n+needle->ptr;
613 hend = h+haystack->ptr;
622 const uschar *npart, *hpart;
624 /* Try to match a non-star part of the needle at the current */
625 /* position in the haystack. */
629 while (npart<nend && *npart != '*') switch (*npart)
633 if (hpart == hend) return 0;
638 /* Match one UTF8 encoded character */
639 if ((*hpart&0xc0) == 0xc0)
642 while (hpart<hend && ((*hpart&0xc0) == 0x80)) ++hpart;
653 if (npart == nend) return -1;
658 if (hpart == hend) return 0;
659 /* tolower depends on the locale, but we need ASCII */
663 (*hpart&0x80) || (*npart&0x80) ||
666 ? ((*npart>= 'A' && *npart<= 'Z' ? *npart|0x20 : *npart) != (*hpart>= 'A' && *hpart<= 'Z' ? *hpart|0x20 : *hpart))
671 /* string match after a star failed, advance and try again */
685 /* at this point, a part was matched successfully */
686 if (may_advance && npart == nend && hpart<hend)
687 /* needle ends, but haystack does not: if there was a star before, advance and try again */
696 return (h == hend ? 1 : may_advance);
700 /*************************************************
701 * ASCII numeric comparison *
702 *************************************************/
706 a first numeric string
707 b second numeric string
708 relop relational operator
710 Returns: 0 not (a relop b)
715 eq_asciinumeric(const gstring *a, const gstring *b, enum RelOp relop)
718 const uschar *as, *aend, *bs, *bend;
726 while (*as>= '0' && *as<= '9' && as<aend) ++as;
728 while (*bs>= '0' && *bs<= '9' && bs<bend) ++bs;
731 if (al && bl == 0) cmp = -1;
732 else if (al == 0 && bl == 0) cmp = 0;
733 else if (al == 0 && bl) cmp = 1;
737 if (cmp == 0) cmp = memcmp(a->s, b->s, al);
741 case LT: return cmp < 0;
742 case LE: return cmp <= 0;
743 case EQ: return cmp == 0;
744 case GE: return cmp >= 0;
745 case GT: return cmp > 0;
746 case NE: return cmp != 0;
753 /*************************************************
755 *************************************************/
759 filter points to the Sieve filter including its state
760 needle UTF-8 pattern or string to search ...
761 haystack ... inside the haystack
765 Returns: 0 needle not found in haystack
767 -1 comparator does not offer matchtype
771 compare(struct Sieve * filter, const gstring * needle, const gstring * haystack,
772 enum Comparator co, enum MatchType mt)
776 if ( (filter_test != FTEST_NONE && debug_selector != 0)
777 || (debug_selector & D_filter) != 0)
779 debug_printf_indent("String comparison (match ");
782 case MATCH_IS: debug_printf_indent(":is"); break;
783 case MATCH_CONTAINS: debug_printf_indent(":contains"); break;
784 case MATCH_MATCHES: debug_printf_indent(":matches"); break;
786 debug_printf_indent(", comparison \"");
789 case COMP_OCTET: debug_printf_indent("i;octet"); break;
790 case COMP_EN_ASCII_CASEMAP: debug_printf_indent("en;ascii-casemap"); break;
791 case COMP_ASCII_NUMERIC: debug_printf_indent("i;ascii-numeric"); break;
793 debug_printf_indent("\"):\n");
794 debug_printf_indent(" Search = %s (%d chars)\n", needle->s, needle->ptr);
795 debug_printf_indent(" Inside = %s (%d chars)\n", haystack->s, haystack->ptr);
803 if (eq_octet(needle, haystack, FALSE)) r = 1;
805 case COMP_EN_ASCII_CASEMAP:
806 if (eq_asciicase(needle, haystack, FALSE)) r = 1;
808 case COMP_ASCII_NUMERIC:
809 if (!filter->require_iascii_numeric)
811 filter->errmsg = CUS "missing previous require \"comparator-i;ascii-numeric\";";
814 if (eq_asciinumeric(needle, haystack, EQ)) r = 1;
826 for (h = *haystack; h.ptr; ++h.s, --h.ptr)
827 if (eq_octet(needle, &h, TRUE)) { r = 1; break; }
829 case COMP_EN_ASCII_CASEMAP:
830 for (h = *haystack; h.ptr; ++h.s, --h.ptr)
831 if (eq_asciicase(needle, &h, TRUE)) { r = 1; break; }
834 filter->errmsg = CUS "comparator does not offer specified matchtype";
844 if ((r = eq_glob(needle, haystack, FALSE, TRUE)) == -1)
846 filter->errmsg = CUS "syntactically invalid pattern";
850 case COMP_EN_ASCII_CASEMAP:
851 if ((r = eq_glob(needle, haystack, TRUE, TRUE)) == -1)
853 filter->errmsg = CUS "syntactically invalid pattern";
858 filter->errmsg = CUS "comparator does not offer specified matchtype";
863 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
864 (debug_selector & D_filter) != 0)
865 debug_printf_indent(" Result %s\n", r?"true":"false");
870 /*************************************************
871 * Check header field syntax *
872 *************************************************/
875 RFC 2822, section 3.6.8 says:
879 ftext = %d33-57 / ; Any character except
880 %d59-126 ; controls, SP, and
883 That forbids 8-bit header fields. This implementation accepts them, since
884 all of Exim is 8-bit clean, so it adds %d128-%d255.
887 header header field to quote for suitable use in Exim expansions
889 Returns: 0 string is not a valid header field
890 1 string is a value header field
894 is_header(const gstring *header)
901 if (l == 0) return 0;
904 if (*h < 33 || *h == ':' || *h == 127)
913 /*************************************************
914 * Quote special characters string *
915 *************************************************/
919 header header field to quote for suitable use in Exim expansions
922 Returns: quoted string
925 static const uschar *
926 quote(const gstring * header)
928 gstring * quoted = NULL;
932 for (l = header->ptr, h = header->s; l; ++h, --l)
936 quoted = string_catn(quoted, CUS "\\0", 2);
941 quoted = string_catn(quoted, CUS "\\", 1);
943 quoted = string_catn(quoted, h, 1);
946 return string_from_gstring(quoted);
950 /*************************************************
951 * Add address to list of generated addresses *
952 *************************************************/
955 According to RFC 5228, duplicate delivery to the same address must
956 not happen, so the list is first searched for the address.
959 generated list of generated addresses
960 addr new address to add
961 file address denotes a file
967 add_addr(address_item ** generated, const uschar * addr, int file, int maxage,
968 int maxmessages, int maxstorage)
970 address_item * new_addr;
972 for (new_addr = *generated; new_addr; new_addr = new_addr->next)
973 if ( Ustrcmp(new_addr->address, addr) == 0
975 || testflag(new_addr, af_pfr)
976 || testflag(new_addr, af_file)
980 if ( filter_test != FTEST_NONE && debug_selector != 0
981 || (debug_selector & D_filter) != 0)
982 debug_printf_indent("Repeated %s `%s' ignored.\n",
983 file ? "fileinto" : "redirect", addr);
988 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
989 debug_printf_indent("%s `%s'\n", file ? "fileinto" : "redirect", addr);
991 new_addr = deliver_make_addr(addr, TRUE);
994 setflag(new_addr, af_pfr);
995 setflag(new_addr, af_file);
998 new_addr->prop.errors_address = NULL;
999 new_addr->next = *generated;
1000 *generated = new_addr;
1004 /*************************************************
1005 * Return decoded header field *
1006 *************************************************/
1009 Unfold the header field as described in RFC 2822 and remove all
1010 leading and trailing white space, then perform MIME decoding and
1011 translate the header field to UTF-8.
1014 value returned value of the field
1015 header name of the header field
1017 Returns: nothing The expanded string is empty
1018 in case there is no such header
1022 expand_header(gstring * value, const gstring * header)
1028 value->s = (uschar*)0;
1030 t = r = s = expand_string(string_sprintf("$rheader_%s", quote(header)));
1032 while (*r == ' ' || *r == '\t') ++r;
1039 while (t>s && (*(t-1) == ' ' || *(t-1) == '\t')) --t;
1041 value->s = rfc2047_decode(s, check_rfc2047_length, US"utf-8", '\0', &value->ptr, &errmsg);
1045 /*************************************************
1046 * Parse remaining hash comment *
1047 *************************************************/
1051 Comment up to terminating CRLF
1054 filter points to the Sieve filter including its state
1061 parse_hashcomment(struct Sieve * filter)
1067 if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
1069 if (*filter->pc == '\n')
1082 filter->errmsg = CUS "missing end of comment";
1087 /*************************************************
1088 * Parse remaining C-style comment *
1089 *************************************************/
1093 Everything up to star slash
1096 filter points to the Sieve filter including its state
1103 parse_comment(struct Sieve *filter)
1107 if (*filter->pc == '*' && (filter->pc)[1] == '/')
1115 filter->errmsg = CUS "missing end of comment";
1120 /*************************************************
1121 * Parse optional white space *
1122 *************************************************/
1126 Spaces, tabs, CRLFs, hash comments or C-style comments
1129 filter points to the Sieve filter including its state
1136 parse_white(struct Sieve *filter)
1140 if (*filter->pc == ' ' || *filter->pc == '\t') ++filter->pc;
1142 else if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
1144 else if (*filter->pc == '\n')
1154 else if (*filter->pc == '#')
1156 if (parse_hashcomment(filter) == -1) return -1;
1158 else if (*filter->pc == '/' && (filter->pc)[1] == '*')
1160 if (parse_comment(filter) == -1) return -1;
1168 #ifdef ENCODED_CHARACTER
1169 /*************************************************
1170 * Decode hex-encoded-character string *
1171 *************************************************/
1174 Encoding definition:
1175 blank = SP / TAB / CRLF
1176 hex-pair-seq = *blank hex-pair *(1*blank hex-pair) *blank
1177 hex-pair = 1*2HEXDIG
1180 src points to a hex-pair-seq
1181 end points to its end
1182 dst points to the destination of the decoded octets,
1183 optionally to (uschar*)0 for checking only
1185 Returns: >= 0 number of decoded octets
1190 hex_decode(uschar *src, uschar *end, uschar *dst)
1194 while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
1200 d<2 && src<end && isxdigit(n = tolower(*src));
1201 x = (x<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')) , ++d, ++src) ;
1202 if (d == 0) return -1;
1203 if (dst) *dst++ = x;
1205 if (src == end) return decoded;
1206 if (*src == ' ' || *src == '\t' || *src == '\n')
1207 while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
1216 /*************************************************
1217 * Decode unicode-encoded-character string *
1218 *************************************************/
1221 Encoding definition:
1222 blank = SP / TAB / CRLF
1223 unicode-hex-seq = *blank unicode-hex *(blank unicode-hex) *blank
1224 unicode-hex = 1*HEXDIG
1226 It is an error for a script to use a hexadecimal value that isn't in
1227 either the range 0 to D7FF or the range E000 to 10FFFF.
1229 At this time, strings are already scanned, thus the CRLF is converted
1230 to the internally used \n (should RFC_EOL have been used).
1233 src points to a unicode-hex-seq
1234 end points to its end
1235 dst points to the destination of the decoded octets,
1236 optionally to (uschar*)0 for checking only
1238 Returns: >= 0 number of decoded octets
1240 -2 semantic error (character range violation)
1244 unicode_decode(uschar *src, uschar *end, uschar *dst)
1248 while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
1255 for (hex_seq = src; src < end && *src == '0'; ) src++;
1257 d < 7 && src < end && isxdigit(n = tolower(*src));
1258 c = (c<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')), ++d, ++src) ;
1259 if (src == hex_seq) return -1;
1260 if (d == 7 || (!((c >= 0 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0x10ffff)))) return -2;
1263 if (dst) *dst++ = c;
1266 else if (c>= 0x80 && c<= 0x7ff)
1270 *dst++ = 192+(c>>6);
1271 *dst++ = 128+(c&0x3f);
1275 else if (c>= 0x800 && c<= 0xffff)
1279 *dst++ = 224+(c>>12);
1280 *dst++ = 128+((c>>6)&0x3f);
1281 *dst++ = 128+(c&0x3f);
1285 else if (c>= 0x10000 && c<= 0x1fffff)
1289 *dst++ = 240+(c>>18);
1290 *dst++ = 128+((c>>10)&0x3f);
1291 *dst++ = 128+((c>>6)&0x3f);
1292 *dst++ = 128+(c&0x3f);
1296 if (*src == ' ' || *src == '\t' || *src == '\n')
1298 while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
1299 if (src == end) return decoded;
1308 /*************************************************
1309 * Decode encoded-character string *
1310 *************************************************/
1313 Encoding definition:
1314 encoded-arb-octets = "${hex:" hex-pair-seq "}"
1315 encoded-unicode-char = "${unicode:" unicode-hex-seq "}"
1318 encoded points to an encoded string, returns decoded string
1319 filter points to the Sieve filter including its state
1326 string_decode(struct Sieve *filter, gstring *data)
1328 uschar *src, *dst, *end;
1332 end = data->s+data->ptr;
1338 strncmpic(src, US "${hex:", 6) == 0
1339 && (brace = Ustrchr(src+6, '}')) != (uschar*)0
1340 && (hex_decode(src+6, brace, (uschar*)0))>= 0
1343 dst += hex_decode(src+6, brace, dst);
1347 strncmpic(src, US "${unicode:", 10) == 0
1348 && (brace = Ustrchr(src+10, '}')) != (uschar*)0
1351 switch (unicode_decode(src+10, brace, (uschar*)0))
1355 filter->errmsg = CUS "unicode character out of range";
1365 dst += unicode_decode(src+10, brace, dst);
1370 else *dst++ = *src++;
1372 data->ptr = dst-data->s;
1379 /*************************************************
1380 * Parse an optional string *
1381 *************************************************/
1385 quoted-string = DQUOTE *CHAR DQUOTE
1386 ;; in general, \ CHAR inside a string maps to CHAR
1387 ;; so \" maps to " and \\ maps to \
1388 ;; note that newlines and other characters are all allowed
1391 multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
1392 *(multi-line-literal / multi-line-dotstuff)
1394 multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
1395 multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
1396 ;; A line containing only "." ends the multi-line.
1397 ;; Remove a leading '.' if followed by another '.'.
1398 string = quoted-string / multi-line
1401 filter points to the Sieve filter including its state
1402 id specifies identifier to match
1406 0 identifier not matched
1410 parse_string(struct Sieve *filter, gstring *data)
1417 if (*filter->pc == '"') /* quoted string */
1422 if (*filter->pc == '"') /* end of string */
1427 data->ptr = len_string_from_gstring(g, &data->s);
1430 /* that way, there will be at least one character allocated */
1432 #ifdef ENCODED_CHARACTER
1433 if ( filter->require_encoded_character
1434 && string_decode(filter, data) == -1)
1439 else if (*filter->pc == '\\' && (filter->pc)[1]) /* quoted character */
1441 g = string_catn(g, filter->pc+1, 1);
1444 else /* regular character */
1447 if (*filter->pc == '\r' && (filter->pc)[1] == '\n') ++filter->line;
1449 if (*filter->pc == '\n')
1451 g = string_catn(g, US"\r", 1);
1455 g = string_catn(g, filter->pc, 1);
1459 filter->errmsg = CUS "missing end of string";
1462 else if (Ustrncmp(filter->pc, CUS "text:", 5) == 0) /* multiline string */
1465 /* skip optional white space followed by hashed comment or CRLF */
1466 while (*filter->pc == ' ' || *filter->pc == '\t') ++filter->pc;
1467 if (*filter->pc == '#')
1469 if (parse_hashcomment(filter) == -1) return -1;
1472 else if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
1474 else if (*filter->pc == '\n')
1486 filter->errmsg = CUS "syntax error";
1492 if (*filter->pc == '\r' && (filter->pc)[1] == '\n') /* end of line */
1494 if (*filter->pc == '\n') /* end of line */
1497 g = string_catn(g, CUS "\r\n", 2);
1505 if (*filter->pc == '.' && (filter->pc)[1] == '\r' && (filter->pc)[2] == '\n') /* end of string */
1507 if (*filter->pc == '.' && (filter->pc)[1] == '\n') /* end of string */
1511 data->ptr = len_string_from_gstring(g, &data->s);
1514 /* that way, there will be at least one character allocated */
1522 #ifdef ENCODED_CHARACTER
1523 if ( filter->require_encoded_character
1524 && string_decode(filter, data) == -1)
1529 else if (*filter->pc == '.' && (filter->pc)[1] == '.') /* remove dot stuffing */
1531 g = string_catn(g, CUS ".", 1);
1535 else /* regular character */
1537 g = string_catn(g, filter->pc, 1);
1541 filter->errmsg = CUS "missing end of multi line string";
1548 /*************************************************
1549 * Parse a specific identifier *
1550 *************************************************/
1554 identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
1557 filter points to the Sieve filter including its state
1558 id specifies identifier to match
1561 0 identifier not matched
1565 parse_identifier(struct Sieve *filter, const uschar *id)
1567 size_t idlen = Ustrlen(id);
1569 if (strncmpic(US filter->pc, US id, idlen) == 0)
1571 uschar next = filter->pc[idlen];
1573 if ((next>= 'A' && next<= 'Z') || (next>= 'a' && next<= 'z') || next == '_' || (next>= '0' && next<= '9')) return 0;
1574 filter->pc += idlen;
1581 /*************************************************
1583 *************************************************/
1587 number = 1*DIGIT [QUANTIFIER]
1588 QUANTIFIER = "K" / "M" / "G"
1591 filter points to the Sieve filter including its state
1595 -1 no string list found
1599 parse_number(struct Sieve *filter, unsigned long *data)
1603 if (*filter->pc>= '0' && *filter->pc<= '9')
1608 d = Ustrtoul(filter->pc, &e, 10);
1609 if (errno == ERANGE)
1611 filter->errmsg = CUstrerror(ERANGE);
1616 if (*filter->pc == 'K') { u = 1024; ++filter->pc; }
1617 else if (*filter->pc == 'M') { u = 1024*1024; ++filter->pc; }
1618 else if (*filter->pc == 'G') { u = 1024*1024*1024; ++filter->pc; }
1619 if (d>(ULONG_MAX/u))
1621 filter->errmsg = CUstrerror(ERANGE);
1630 filter->errmsg = CUS "missing number";
1636 /*************************************************
1637 * Parse a string list *
1638 *************************************************/
1642 string-list = "[" string *(", " string) "]" / string
1645 filter points to the Sieve filter including its state
1646 data returns string list
1649 -1 no string list found
1653 parse_stringlist(struct Sieve *filter, gstring **data)
1655 const uschar *orig = filter->pc;
1656 int dataCapacity = 0;
1661 if (*filter->pc == '[') /* string list */
1666 if (parse_white(filter) == -1) goto error;
1667 if (dataLength+1 >= dataCapacity) /* increase buffer */
1671 dataCapacity = dataCapacity ? dataCapacity * 2 : 4;
1672 new = store_get(sizeof(gstring) * dataCapacity, GET_UNTAINTED);
1674 if (d) memcpy(new, d, sizeof(gstring)*dataLength);
1678 m = parse_string(filter, &d[dataLength]);
1681 if (dataLength == 0) break;
1684 filter->errmsg = CUS "missing string";
1688 else if (m == -1) goto error;
1690 if (parse_white(filter) == -1) goto error;
1691 if (*filter->pc == ',') ++filter->pc;
1694 if (*filter->pc == ']')
1696 d[dataLength].s = (uschar*)0;
1697 d[dataLength].ptr = -1;
1704 filter->errmsg = CUS "missing closing bracket";
1708 else /* single string */
1710 if (!(d = store_get(sizeof(gstring)*2, GET_UNTAINTED)))
1713 m = parse_string(filter, &d[0]);
1724 d[1].s = (uschar*)0;
1731 filter->errmsg = CUS "missing string list";
1736 /*************************************************
1737 * Parse an optional address part specifier *
1738 *************************************************/
1742 address-part = ":localpart" / ":domain" / ":all"
1743 address-part = / ":user" / ":detail"
1746 filter points to the Sieve filter including its state
1747 a returns address part specified
1750 0 no comparator found
1755 parse_addresspart(struct Sieve *filter, enum AddressPart *a)
1758 if (parse_identifier(filter, CUS ":user") == 1)
1760 if (!filter->require_subaddress)
1762 filter->errmsg = CUS "missing previous require \"subaddress\";";
1768 else if (parse_identifier(filter, CUS ":detail") == 1)
1770 if (!filter->require_subaddress)
1772 filter->errmsg = CUS "missing previous require \"subaddress\";";
1775 *a = ADDRPART_DETAIL;
1780 if (parse_identifier(filter, CUS ":localpart") == 1)
1782 *a = ADDRPART_LOCALPART;
1785 else if (parse_identifier(filter, CUS ":domain") == 1)
1787 *a = ADDRPART_DOMAIN;
1790 else if (parse_identifier(filter, CUS ":all") == 1)
1799 /*************************************************
1800 * Parse an optional comparator *
1801 *************************************************/
1805 comparator = ":comparator" <comparator-name: string>
1808 filter points to the Sieve filter including its state
1809 c returns comparator
1812 0 no comparator found
1813 -1 incomplete comparator found
1817 parse_comparator(struct Sieve *filter, enum Comparator *c)
1819 gstring comparator_name;
1821 if (parse_identifier(filter, CUS ":comparator") == 0) return 0;
1822 if (parse_white(filter) == -1) return -1;
1823 switch (parse_string(filter, &comparator_name))
1828 filter->errmsg = CUS "missing comparator";
1835 if (eq_asciicase(&comparator_name, &str_ioctet, FALSE))
1840 else if (eq_asciicase(&comparator_name, &str_iascii_casemap, FALSE))
1842 *c = COMP_EN_ASCII_CASEMAP;
1845 else if (eq_asciicase(&comparator_name, &str_enascii_casemap, FALSE))
1847 *c = COMP_EN_ASCII_CASEMAP;
1850 else if (eq_asciicase(&comparator_name, &str_iascii_numeric, FALSE))
1852 *c = COMP_ASCII_NUMERIC;
1857 filter->errmsg = CUS "invalid comparator";
1866 /*************************************************
1867 * Parse an optional match type *
1868 *************************************************/
1872 match-type = ":is" / ":contains" / ":matches"
1875 filter points to the Sieve filter including its state
1876 m returns match type
1879 0 no match type found
1883 parse_matchtype(struct Sieve *filter, enum MatchType *m)
1885 if (parse_identifier(filter, CUS ":is") == 1)
1886 { *m = MATCH_IS; return 1; }
1887 else if (parse_identifier(filter, CUS ":contains") == 1)
1888 { *m = MATCH_CONTAINS; return 1; }
1889 else if (parse_identifier(filter, CUS ":matches") == 1)
1890 { *m = MATCH_MATCHES; return 1; }
1895 /*************************************************
1896 * Parse and interpret an optional test list *
1897 *************************************************/
1901 test-list = "(" test *("," test) ")"
1904 filter points to the Sieve filter including its state
1905 n total number of tests
1906 num_true number of passed tests
1907 exec Execute parsed statements
1910 0 no test list found
1911 -1 syntax or execution error
1915 parse_testlist(struct Sieve *filter, int *n, int *num_true, int exec)
1917 if (parse_white(filter) == -1) return -1;
1918 if (*filter->pc == '(')
1927 switch (parse_test(filter, &cond, exec))
1930 case 0: filter->errmsg = CUS "missing test"; return -1;
1931 default: ++*n; if (cond) ++*num_true; break;
1933 if (parse_white(filter) == -1) return -1;
1934 if (*filter->pc == ',') ++filter->pc;
1937 if (*filter->pc == ')')
1944 filter->errmsg = CUS "missing closing paren";
1952 /*************************************************
1953 * Parse and interpret an optional test *
1954 *************************************************/
1958 filter points to the Sieve filter including its state
1959 cond returned condition status
1960 exec Execute parsed statements
1964 -1 syntax or execution error
1968 parse_test(struct Sieve *filter, int *cond, int exec)
1970 if (parse_white(filter) == -1) return -1;
1971 if (parse_identifier(filter, CUS "address"))
1974 address-test = "address" { [address-part] [comparator] [match-type] }
1975 <header-list: string-list> <key-list: string-list>
1977 header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
1980 enum AddressPart addressPart = ADDRPART_ALL;
1981 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
1982 enum MatchType matchType = MATCH_IS;
1985 int ap = 0, co = 0, mt = 0;
1989 if (parse_white(filter) == -1) return -1;
1990 if ((m = parse_addresspart(filter, &addressPart)) != 0)
1992 if (m == -1) return -1;
1995 filter->errmsg = CUS "address part already specified";
2000 else if ((m = parse_comparator(filter, &comparator)) != 0)
2002 if (m == -1) return -1;
2005 filter->errmsg = CUS "comparator already specified";
2010 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2012 if (m == -1) return -1;
2015 filter->errmsg = CUS "match type already specified";
2022 if (parse_white(filter) == -1)
2024 if ((m = parse_stringlist(filter, &hdr)) != 1)
2026 if (m == 0) filter->errmsg = CUS "header string list expected";
2029 if (parse_white(filter) == -1)
2031 if ((m = parse_stringlist(filter, &key)) != 1)
2033 if (m == 0) filter->errmsg = CUS "key string list expected";
2037 for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
2039 uschar * header_value = NULL, * extracted_addr, * end_addr;
2041 if ( !eq_asciicase(h, &str_from, FALSE)
2042 && !eq_asciicase(h, &str_to, FALSE)
2043 && !eq_asciicase(h, &str_cc, FALSE)
2044 && !eq_asciicase(h, &str_bcc, FALSE)
2045 && !eq_asciicase(h, &str_sender, FALSE)
2046 && !eq_asciicase(h, &str_resent_from, FALSE)
2047 && !eq_asciicase(h, &str_resent_to, FALSE)
2050 filter->errmsg = CUS "invalid header field";
2055 /* We are only interested in addresses below, so no MIME decoding */
2056 if (!(header_value = expand_string(string_sprintf("$rheader_%s", quote(h)))))
2058 filter->errmsg = CUS "header string expansion failed";
2061 f.parse_allow_group = TRUE;
2062 while (*header_value && !*cond)
2065 int start, end, domain;
2067 uschar *part = NULL;
2069 end_addr = parse_find_address_end(header_value, FALSE);
2070 saveend = *end_addr;
2072 extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
2074 if (extracted_addr) switch (addressPart)
2076 case ADDRPART_ALL: part = extracted_addr; break;
2080 case ADDRPART_LOCALPART: part = extracted_addr; part[domain-1] = '\0'; break;
2081 case ADDRPART_DOMAIN: part = extracted_addr+domain; break;
2083 case ADDRPART_DETAIL: part = NULL; break;
2087 *end_addr = saveend;
2088 if (part && extracted_addr)
2090 gstring partStr = {.s = part, .ptr = Ustrlen(part), .size = Ustrlen(part)+1};
2091 for (gstring * k = key; k->ptr != - 1; ++k)
2093 *cond = compare(filter, k, &partStr, comparator, matchType);
2094 if (*cond == -1) return -1;
2099 if (saveend == 0) break;
2100 header_value = end_addr + 1;
2102 f.parse_allow_group = FALSE;
2103 f.parse_found_group = FALSE;
2108 else if (parse_identifier(filter, CUS "allof"))
2111 allof-test = "allof" <tests: test-list>
2116 switch (parse_testlist(filter, &n, &num_true, exec))
2119 case 0: filter->errmsg = CUS "missing test list"; return -1;
2120 default: *cond = (n == num_true); return 1;
2123 else if (parse_identifier(filter, CUS "anyof"))
2126 anyof-test = "anyof" <tests: test-list>
2131 switch (parse_testlist(filter, &n, &num_true, exec))
2134 case 0: filter->errmsg = CUS "missing test list"; return -1;
2135 default: *cond = (num_true>0); return 1;
2138 else if (parse_identifier(filter, CUS "exists"))
2141 exists-test = "exists" <header-names: string-list>
2147 if (parse_white(filter) == -1)
2149 if ((m = parse_stringlist(filter, &hdr)) != 1)
2151 if (m == 0) filter->errmsg = CUS "header string list expected";
2157 for (gstring * h = hdr; h->ptr != -1 && *cond; ++h)
2161 header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
2164 filter->errmsg = CUS "header string expansion failed";
2167 if (Ustrcmp(header_def,"false") == 0) *cond = 0;
2172 else if (parse_identifier(filter, CUS "false"))
2175 false-test = "false"
2181 else if (parse_identifier(filter, CUS "header"))
2184 header-test = "header" { [comparator] [match-type] }
2185 <header-names: string-list> <key-list: string-list>
2188 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2189 enum MatchType matchType = MATCH_IS;
2196 if (parse_white(filter) == -1)
2198 if ((m = parse_comparator(filter, &comparator)) != 0)
2200 if (m == -1) return -1;
2203 filter->errmsg = CUS "comparator already specified";
2208 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2210 if (m == -1) return -1;
2213 filter->errmsg = CUS "match type already specified";
2220 if (parse_white(filter) == -1)
2222 if ((m = parse_stringlist(filter, &hdr)) != 1)
2224 if (m == 0) filter->errmsg = CUS "header string list expected";
2227 if (parse_white(filter) == -1)
2229 if ((m = parse_stringlist(filter, &key)) != 1)
2231 if (m == 0) filter->errmsg = CUS "key string list expected";
2235 for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
2239 filter->errmsg = CUS "invalid header field";
2244 gstring header_value;
2247 expand_header(&header_value, h);
2248 header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
2249 if (!header_value.s || !header_def)
2251 filter->errmsg = CUS "header string expansion failed";
2254 for (gstring * k = key; k->ptr != -1; ++k)
2255 if (Ustrcmp(header_def,"true") == 0)
2257 *cond = compare(filter, k, &header_value, comparator, matchType);
2258 if (*cond == -1) return -1;
2265 else if (parse_identifier(filter, CUS "not"))
2267 if (parse_white(filter) == -1) return -1;
2268 switch (parse_test(filter, cond, exec))
2271 case 0: filter->errmsg = CUS "missing test"; return -1;
2272 default: *cond = !*cond; return 1;
2275 else if (parse_identifier(filter, CUS "size"))
2278 relop = ":over" / ":under"
2279 size-test = "size" relop <limit: number>
2282 unsigned long limit;
2285 if (parse_white(filter) == -1) return -1;
2286 if (parse_identifier(filter, CUS ":over")) overNotUnder = 1;
2287 else if (parse_identifier(filter, CUS ":under")) overNotUnder = 0;
2290 filter->errmsg = CUS "missing :over or :under";
2293 if (parse_white(filter) == -1) return -1;
2294 if (parse_number(filter, &limit) == -1) return -1;
2295 *cond = (overNotUnder ? (message_size>limit) : (message_size<limit));
2298 else if (parse_identifier(filter, CUS "true"))
2303 else if (parse_identifier(filter, CUS "envelope"))
2306 envelope-test = "envelope" { [comparator] [address-part] [match-type] }
2307 <envelope-part: string-list> <key-list: string-list>
2309 envelope-part is case insensitive "from" or "to"
2310 #ifdef ENVELOPE_AUTH
2311 envelope-part = / "auth"
2315 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2316 enum AddressPart addressPart = ADDRPART_ALL;
2317 enum MatchType matchType = MATCH_IS;
2320 int co = 0, ap = 0, mt = 0;
2322 if (!filter->require_envelope)
2324 filter->errmsg = CUS "missing previous require \"envelope\";";
2329 if (parse_white(filter) == -1) return -1;
2330 if ((m = parse_comparator(filter, &comparator)) != 0)
2332 if (m == -1) return -1;
2335 filter->errmsg = CUS "comparator already specified";
2340 else if ((m = parse_addresspart(filter, &addressPart)) != 0)
2342 if (m == -1) return -1;
2345 filter->errmsg = CUS "address part already specified";
2350 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2352 if (m == -1) return -1;
2355 filter->errmsg = CUS "match type already specified";
2362 if (parse_white(filter) == -1)
2364 if ((m = parse_stringlist(filter, &env)) != 1)
2366 if (m == 0) filter->errmsg = CUS "envelope string list expected";
2369 if (parse_white(filter) == -1)
2371 if ((m = parse_stringlist(filter, &key)) != 1)
2373 if (m == 0) filter->errmsg = CUS "key string list expected";
2377 for (gstring * e = env; e->ptr != -1 && !*cond; ++e)
2379 const uschar *envelopeExpr = CUS 0;
2380 uschar *envelope = US 0;
2382 if (eq_asciicase(e, &str_from, FALSE))
2384 switch (addressPart)
2386 case ADDRPART_ALL: envelopeExpr = CUS "$sender_address"; break;
2390 case ADDRPART_LOCALPART: envelopeExpr = CUS "${local_part:$sender_address}"; break;
2391 case ADDRPART_DOMAIN: envelopeExpr = CUS "${domain:$sender_address}"; break;
2393 case ADDRPART_DETAIL: envelopeExpr = CUS 0; break;
2397 else if (eq_asciicase(e, &str_to, FALSE))
2399 switch (addressPart)
2401 case ADDRPART_ALL: envelopeExpr = CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
2403 case ADDRPART_USER: envelopeExpr = filter->useraddress; break;
2404 case ADDRPART_DETAIL: envelopeExpr = filter->subaddress; break;
2406 case ADDRPART_LOCALPART: envelopeExpr = CUS "$local_part_prefix$local_part$local_part_suffix"; break;
2407 case ADDRPART_DOMAIN: envelopeExpr = CUS "$domain"; break;
2410 #ifdef ENVELOPE_AUTH
2411 else if (eq_asciicase(e, &str_auth, FALSE))
2413 switch (addressPart)
2415 case ADDRPART_ALL: envelopeExpr = CUS "$authenticated_sender"; break;
2419 case ADDRPART_LOCALPART: envelopeExpr = CUS "${local_part:$authenticated_sender}"; break;
2420 case ADDRPART_DOMAIN: envelopeExpr = CUS "${domain:$authenticated_sender}"; break;
2422 case ADDRPART_DETAIL: envelopeExpr = CUS 0; break;
2429 filter->errmsg = CUS "invalid envelope string";
2432 if (exec && envelopeExpr)
2434 if (!(envelope = expand_string(US envelopeExpr)))
2436 filter->errmsg = CUS "header string expansion failed";
2439 for (gstring * k = key; k->ptr != -1; ++k)
2441 gstring envelopeStr = {.s = envelope, .ptr = Ustrlen(envelope), .size = Ustrlen(envelope)+1};
2443 *cond = compare(filter, k, &envelopeStr, comparator, matchType);
2444 if (*cond == -1) return -1;
2452 else if (parse_identifier(filter, CUS "valid_notify_method"))
2455 valid_notify_method = "valid_notify_method"
2456 <notification-uris: string-list>
2462 if (!filter->require_enotify)
2464 filter->errmsg = CUS "missing previous require \"enotify\";";
2467 if (parse_white(filter) == -1)
2469 if ((m = parse_stringlist(filter, &uris)) != 1)
2471 if (m == 0) filter->errmsg = CUS "URI string list expected";
2477 for (gstring * u = uris; u->ptr != -1 && *cond; ++u)
2479 string_item * recipient = NULL;
2480 gstring header = { .s = NULL, .ptr = -1 };
2481 gstring subject = { .s = NULL, .ptr = -1 };
2482 gstring body = { .s = NULL, .ptr = -1 };
2484 if (parse_mailto_uri(filter, u->s, &recipient, &header, &subject, &body) != 1)
2490 else if (parse_identifier(filter, CUS "notify_method_capability"))
2493 notify_method_capability = "notify_method_capability" [COMPARATOR] [MATCH-TYPE]
2494 <notification-uri: string>
2495 <notification-capability: string>
2496 <key-list: string-list>
2502 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2503 enum MatchType matchType = MATCH_IS;
2504 gstring uri, capa, *keys;
2506 if (!filter->require_enotify)
2508 filter->errmsg = CUS "missing previous require \"enotify\";";
2513 if (parse_white(filter) == -1) return -1;
2514 if ((m = parse_comparator(filter, &comparator)) != 0)
2516 if (m == -1) return -1;
2519 filter->errmsg = CUS "comparator already specified";
2524 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2526 if (m == -1) return -1;
2529 filter->errmsg = CUS "match type already specified";
2536 if ((m = parse_string(filter, &uri)) != 1)
2538 if (m == 0) filter->errmsg = CUS "missing notification URI string";
2541 if (parse_white(filter) == -1)
2543 if ((m = parse_string(filter, &capa)) != 1)
2545 if (m == 0) filter->errmsg = CUS "missing notification capability string";
2548 if (parse_white(filter) == -1)
2550 if ((m = parse_stringlist(filter, &keys)) != 1)
2552 if (m == 0) filter->errmsg = CUS "missing key string list";
2557 string_item * recipient = NULL;
2558 gstring header = { .s = NULL, .ptr = -1 };
2559 gstring subject = { .s = NULL, .ptr = -1 };
2560 gstring body = { .s = NULL, .ptr = -1 };
2563 if (parse_mailto_uri(filter, uri.s, &recipient, &header, &subject, &body) == 1)
2564 if (eq_asciicase(&capa, &str_online, FALSE) == 1)
2565 for (gstring * k = keys; k->ptr != -1; ++k)
2567 *cond = compare(filter, k, &str_maybe, comparator, matchType);
2568 if (*cond == -1) return -1;
2579 /*************************************************
2580 * Parse and interpret an optional block *
2581 *************************************************/
2585 filter points to the Sieve filter including its state
2586 exec Execute parsed statements
2587 generated where to hang newly-generated addresses
2589 Returns: 2 success by stop
2591 0 no block command found
2592 -1 syntax or execution error
2596 parse_block(struct Sieve * filter, int exec, address_item ** generated)
2600 if (parse_white(filter) == -1)
2602 if (*filter->pc == '{')
2605 if ((r = parse_commands(filter, exec, generated)) == -1 || r == 2) return r;
2606 if (*filter->pc == '}')
2611 filter->errmsg = CUS "expecting command or closing brace";
2618 /*************************************************
2619 * Match a semicolon *
2620 *************************************************/
2624 filter points to the Sieve filter including its state
2631 parse_semicolon(struct Sieve *filter)
2633 if (parse_white(filter) == -1)
2635 if (*filter->pc == ';')
2640 filter->errmsg = CUS "missing semicolon";
2645 /*************************************************
2646 * Parse and interpret a Sieve command *
2647 *************************************************/
2651 filter points to the Sieve filter including its state
2652 exec Execute parsed statements
2653 generated where to hang newly-generated addresses
2655 Returns: 2 success by stop
2657 -1 syntax or execution error
2660 parse_commands(struct Sieve *filter, int exec, address_item **generated)
2664 if (parse_white(filter) == -1)
2666 if (parse_identifier(filter, CUS "if"))
2669 if-command = "if" test block *( "elsif" test block ) [ else block ]
2672 int cond, m, unsuccessful;
2675 if (parse_white(filter) == -1)
2677 if ((m = parse_test(filter, &cond, exec)) == -1)
2681 filter->errmsg = CUS "missing test";
2684 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
2685 (debug_selector & D_filter) != 0)
2687 if (exec) debug_printf_indent("if %s\n", cond?"true":"false");
2689 m = parse_block(filter, exec ? cond : 0, generated);
2690 if (m == -1 || m == 2)
2694 filter->errmsg = CUS "missing block";
2697 unsuccessful = !cond;
2698 for (;;) /* elsif test block */
2700 if (parse_white(filter) == -1)
2702 if (parse_identifier(filter, CUS "elsif"))
2704 if (parse_white(filter) == -1)
2706 m = parse_test(filter, &cond, exec && unsuccessful);
2707 if (m == -1 || m == 2)
2711 filter->errmsg = CUS "missing test";
2714 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
2715 (debug_selector & D_filter) != 0)
2717 if (exec) debug_printf_indent("elsif %s\n", cond?"true":"false");
2719 m = parse_block(filter, exec && unsuccessful ? cond : 0, generated);
2720 if (m == -1 || m == 2)
2724 filter->errmsg = CUS "missing block";
2727 if (exec && unsuccessful && cond)
2733 if (parse_white(filter) == -1)
2735 if (parse_identifier(filter, CUS "else"))
2737 m = parse_block(filter, exec && unsuccessful, generated);
2738 if (m == -1 || m == 2)
2742 filter->errmsg = CUS "missing block";
2747 else if (parse_identifier(filter, CUS "stop"))
2750 stop-command = "stop" { stop-options } ";"
2754 if (parse_semicolon(filter) == -1)
2758 filter->pc += Ustrlen(filter->pc);
2762 else if (parse_identifier(filter, CUS "keep"))
2765 keep-command = "keep" { keep-options } ";"
2769 if (parse_semicolon(filter) == -1)
2773 add_addr(generated, filter->inbox, 1, 0, 0, 0);
2777 else if (parse_identifier(filter, CUS "discard"))
2780 discard-command = "discard" { discard-options } ";"
2784 if (parse_semicolon(filter) == -1)
2786 if (exec) filter->keep = 0;
2788 else if (parse_identifier(filter, CUS "redirect"))
2791 redirect-command = "redirect" redirect-options "string" ";"
2793 redirect-options = ) ":copy"
2802 if (parse_white(filter) == -1)
2804 if (parse_identifier(filter, CUS ":copy") == 1)
2806 if (!filter->require_copy)
2808 filter->errmsg = CUS "missing previous require \"copy\";";
2815 if (parse_white(filter) == -1)
2817 if ((m = parse_string(filter, &recipient)) != 1)
2820 filter->errmsg = CUS "missing redirect recipient string";
2823 if (strchr(CCS recipient.s, '@') == NULL)
2825 filter->errmsg = CUS "unqualified recipient address";
2830 add_addr(generated, recipient.s, 0, 0, 0, 0);
2831 if (!copy) filter->keep = 0;
2833 if (parse_semicolon(filter) == -1) return -1;
2835 else if (parse_identifier(filter, CUS "fileinto"))
2838 fileinto-command = "fileinto" { fileinto-options } string ";"
2840 fileinto-options = ) [ ":copy" ]
2846 unsigned long maxage, maxmessages, maxstorage;
2849 maxage = maxmessages = maxstorage = 0;
2850 if (!filter->require_fileinto)
2852 filter->errmsg = CUS "missing previous require \"fileinto\";";
2857 if (parse_white(filter) == -1)
2859 if (parse_identifier(filter, CUS ":copy") == 1)
2861 if (!filter->require_copy)
2863 filter->errmsg = CUS "missing previous require \"copy\";";
2870 if (parse_white(filter) == -1)
2872 if ((m = parse_string(filter, &folder)) != 1)
2874 if (m == 0) filter->errmsg = CUS "missing fileinto folder string";
2877 m = 0; s = folder.s;
2878 if (folder.ptr == 0)
2880 if (Ustrcmp(s,"..") == 0 || Ustrncmp(s,"../", 3) == 0)
2884 if (Ustrcmp(s,"/..") == 0 || Ustrncmp(s,"/../", 4) == 0) { m = 1; break; }
2889 filter->errmsg = CUS "invalid folder";
2894 add_addr(generated, folder.s, 1, maxage, maxmessages, maxstorage);
2895 if (!copy) filter->keep = 0;
2897 if (parse_semicolon(filter) == -1)
2901 else if (parse_identifier(filter, CUS "notify"))
2904 notify-command = "notify" { notify-options } <method: string> ";"
2905 notify-options = [":from" string]
2906 [":importance" <"1" / "2" / "3">]
2907 [":options" 1*(string-list / number)]
2912 gstring from = { .s = NULL, .ptr = -1 };
2913 gstring importance = { .s = NULL, .ptr = -1 };
2914 gstring message = { .s = NULL, .ptr = -1 };
2916 struct Notification *already;
2917 string_item * recipient = NULL;
2918 gstring header = { .s = NULL, .ptr = -1 };
2919 gstring subject = { .s = NULL, .ptr = -1 };
2920 gstring body = { .s = NULL, .ptr = -1 };
2921 uschar *envelope_from;
2922 gstring auto_submitted_value;
2923 uschar *auto_submitted_def;
2925 if (!filter->require_enotify)
2927 filter->errmsg = CUS "missing previous require \"enotify\";";
2930 envelope_from = sender_address && sender_address[0]
2931 ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : US "";
2934 filter->errmsg = CUS "expansion failure for envelope from";
2939 if (parse_white(filter) == -1)
2941 if (parse_identifier(filter, CUS ":from") == 1)
2943 if (parse_white(filter) == -1)
2945 if ((m = parse_string(filter, &from)) != 1)
2947 if (m == 0) filter->errmsg = CUS "from string expected";
2951 else if (parse_identifier(filter, CUS ":importance") == 1)
2953 if (parse_white(filter) == -1)
2955 if ((m = parse_string(filter, &importance)) != 1)
2958 filter->errmsg = CUS "importance string expected";
2961 if (importance.ptr != 1 || importance.s[0] < '1' || importance.s[0] > '3')
2963 filter->errmsg = CUS "invalid importance";
2967 else if (parse_identifier(filter, CUS ":options") == 1)
2969 if (parse_white(filter) == -1)
2972 else if (parse_identifier(filter, CUS ":message") == 1)
2974 if (parse_white(filter) == -1)
2976 if ((m = parse_string(filter, &message)) != 1)
2979 filter->errmsg = CUS "message string expected";
2985 if (parse_white(filter) == -1)
2987 if ((m = parse_string(filter, &method)) != 1)
2990 filter->errmsg = CUS "missing method string";
2993 if (parse_semicolon(filter) == -1)
2995 if (parse_mailto_uri(filter, method.s, &recipient, &header, &subject, &body) != 1)
2999 if (message.ptr == -1)
3001 if (message.ptr == -1)
3002 expand_header(&message, &str_subject);
3003 expand_header(&auto_submitted_value, &str_auto_submitted);
3004 auto_submitted_def = expand_string(US"${if def:header_auto-submitted {true}{false}}");
3005 if (!auto_submitted_value.s || !auto_submitted_def)
3007 filter->errmsg = CUS "header string expansion failed";
3010 if (Ustrcmp(auto_submitted_def,"true") != 0 || Ustrcmp(auto_submitted_value.s,"no") == 0)
3012 for (already = filter->notified; already; already = already->next)
3014 if ( already->method.ptr == method.ptr
3015 && (method.ptr == -1 || Ustrcmp(already->method.s, method.s) == 0)
3016 && already->importance.ptr == importance.ptr
3017 && (importance.ptr == -1 || Ustrcmp(already->importance.s, importance.s) == 0)
3018 && already->message.ptr == message.ptr
3019 && (message.ptr == -1 || Ustrcmp(already->message.s, message.s) == 0))
3023 /* New notification, process it */
3025 struct Notification * sent = store_get(sizeof(struct Notification), GET_UNTAINTED);
3026 sent->method = method;
3027 sent->importance = importance;
3028 sent->message = message;
3029 sent->next = filter->notified;
3030 filter->notified = sent;
3031 #ifndef COMPILE_SYNTAX_CHECKER
3032 if (filter_test == FTEST_NONE)
3036 if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
3037 US"sieve-notify")) >= 1)
3039 FILE * f = fdopen(fd, "wb");
3041 fprintf(f,"From: %s\n", from.ptr == -1
3042 ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain")
3044 for (string_item * p = recipient; p; p = p->next)
3045 fprintf(f, "To: %s\n", p->text);
3046 fprintf(f, "Auto-Submitted: auto-notified; %s\n", filter->enotify_mailto_owner);
3047 if (header.ptr > 0) fprintf(f, "%s", header.s);
3048 if (message.ptr == -1)
3050 message.s = US"Notification";
3051 message.ptr = Ustrlen(message.s);
3053 if (message.ptr != -1)
3054 fprintf(f, "Subject: %s\n", parse_quote_2047(message.s,
3055 message.ptr, US"utf-8", TRUE));
3057 if (body.ptr > 0) fprintf(f, "%s\n", body.s);
3060 (void)child_close(pid, 0);
3063 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3064 debug_printf_indent("Notification to `%s': '%s'.\n", method.s, message.ptr != -1 ? message.s : CUS "");
3068 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3069 debug_printf_indent("Repeated notification to `%s' ignored.\n", method.s);
3072 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3073 debug_printf_indent("Ignoring notification, triggering message contains Auto-submitted: field.\n");
3078 else if (parse_identifier(filter, CUS "vacation"))
3081 vacation-command = "vacation" { vacation-options } <reason: string> ";"
3082 vacation-options = [":days" number]
3085 [":addresses" string-list]
3096 string_item *aliases;
3100 if (!filter->require_vacation)
3102 filter->errmsg = CUS "missing previous require \"vacation\";";
3107 if (filter->vacation_ran)
3109 filter->errmsg = CUS "trying to execute vacation more than once";
3112 filter->vacation_ran = TRUE;
3114 days = VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
3115 subject.s = (uschar*)0;
3117 from.s = (uschar*)0;
3119 addresses = (gstring*)0;
3122 handle.s = (uschar*)0;
3126 if (parse_white(filter) == -1)
3128 if (parse_identifier(filter, CUS ":days") == 1)
3130 if (parse_white(filter) == -1)
3132 if (parse_number(filter, &days) == -1)
3134 if (days<VACATION_MIN_DAYS)
3135 days = VACATION_MIN_DAYS;
3136 else if (days>VACATION_MAX_DAYS)
3137 days = VACATION_MAX_DAYS;
3139 else if (parse_identifier(filter, CUS ":subject") == 1)
3141 if (parse_white(filter) == -1)
3143 if ((m = parse_string(filter, &subject)) != 1)
3146 filter->errmsg = CUS "subject string expected";
3150 else if (parse_identifier(filter, CUS ":from") == 1)
3152 if (parse_white(filter) == -1)
3154 if ((m = parse_string(filter, &from)) != 1)
3157 filter->errmsg = CUS "from string expected";
3160 if (check_mail_address(filter, &from) != 1)
3163 else if (parse_identifier(filter, CUS ":addresses") == 1)
3165 if (parse_white(filter) == -1)
3167 if ((m = parse_stringlist(filter, &addresses)) != 1)
3170 filter->errmsg = CUS "addresses string list expected";
3173 for (gstring * a = addresses; a->ptr != -1; ++a)
3175 string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
3177 new->text = store_get(a->ptr+1, a->s);
3178 if (a->ptr) memcpy(new->text, a->s, a->ptr);
3179 new->text[a->ptr] = '\0';
3180 new->next = aliases;
3184 else if (parse_identifier(filter, CUS ":mime") == 1)
3186 else if (parse_identifier(filter, CUS ":handle") == 1)
3188 if (parse_white(filter) == -1)
3190 if ((m = parse_string(filter, &from)) != 1)
3193 filter->errmsg = CUS "handle string expected";
3199 if (parse_white(filter) == -1)
3201 if ((m = parse_string(filter, &reason)) != 1)
3204 filter->errmsg = CUS "missing reason string";
3211 for (s = reason.s, end = reason.s + reason.ptr;
3212 s<end && (*s&0x80) == 0; ) s++;
3215 filter->errmsg = CUS "MIME reason string contains 8bit text";
3219 if (parse_semicolon(filter) == -1) return -1;
3226 uschar hexdigest[33];
3229 if (filter_personal(aliases, TRUE))
3231 if (filter_test == FTEST_NONE)
3233 /* ensure oncelog directory exists; failure will be detected later */
3235 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
3237 /* build oncelog filename */
3241 if (handle.ptr == -1)
3243 gstring * key = NULL;
3244 if (subject.ptr != -1)
3245 key = string_catn(key, subject.s, subject.ptr);
3247 key = string_catn(key, from.s, from.ptr);
3248 key = string_catn(key, reason_is_mime?US"1":US"0", 1);
3249 key = string_catn(key, reason.s, reason.ptr);
3250 md5_end(&base, key->s, key->ptr, digest);
3253 md5_end(&base, handle.s, handle.ptr, digest);
3255 for (int i = 0; i < 16; i++)
3256 sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
3258 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
3259 debug_printf_indent("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
3261 if (filter_test == FTEST_NONE)
3263 once = string_cat (NULL, filter->vacation_directory);
3264 once = string_catn(once, US"/", 1);
3265 once = string_catn(once, hexdigest, 33);
3267 /* process subject */
3269 if (subject.ptr == -1)
3271 uschar * subject_def;
3273 subject_def = expand_string(US"${if def:header_subject {true}{false}}");
3274 if (subject_def && Ustrcmp(subject_def,"true") == 0)
3276 gstring * g = string_catn(NULL, US"Auto: ", 6);
3278 expand_header(&subject, &str_subject);
3279 g = string_catn(g, subject.s, subject.ptr);
3280 subject.ptr = len_string_from_gstring(g, &subject.s);
3284 subject.s = US"Automated reply";
3285 subject.ptr = Ustrlen(subject.s);
3289 /* add address to list of generated addresses */
3291 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
3292 setflag(addr, af_pfr);
3293 addr->prop.ignore_error = TRUE;
3294 addr->next = *generated;
3296 addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED);
3297 memset(addr->reply, 0, sizeof(reply_item)); /* XXX */
3298 addr->reply->to = string_copy(sender_address);
3300 addr->reply->from = expand_string(US"$local_part@$domain");
3302 addr->reply->from = from.s;
3303 /* deconst cast safe as we pass in a non-const item */
3304 addr->reply->subject = US parse_quote_2047(subject.s, subject.ptr, US"utf-8", TRUE);
3305 addr->reply->oncelog = string_from_gstring(once);
3306 addr->reply->once_repeat = days*86400;
3308 /* build body and MIME headers */
3312 uschar *mime_body, *reason_end;
3313 static const uschar nlnl[] = "\r\n\r\n";
3317 mime_body = reason.s, reason_end = reason.s + reason.ptr;
3318 mime_body < (reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body, nlnl, (sizeof(nlnl)-1));
3321 addr->reply->headers = string_copyn(reason.s, mime_body-reason.s);
3323 if (mime_body+(sizeof(nlnl)-1)<reason_end)
3324 mime_body += (sizeof(nlnl)-1);
3325 else mime_body = reason_end-1;
3326 addr->reply->text = string_copyn(mime_body, reason_end-mime_body);
3330 addr->reply->headers = US"MIME-Version: 1.0\n"
3331 "Content-Type: text/plain;\n"
3332 "\tcharset=\"utf-8\"\n"
3333 "Content-Transfer-Encoding: quoted-printable";
3334 addr->reply->text = quoted_printable_encode(&reason)->s;
3338 else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
3339 debug_printf_indent("Sieve: mail was not personal, vacation would ignore it\n");
3349 /*************************************************
3350 * Parse and interpret a sieve filter *
3351 *************************************************/
3355 filter points to the Sieve filter including its state
3356 exec Execute parsed statements
3357 generated where to hang newly-generated addresses
3360 -1 syntax or execution error
3364 parse_start(struct Sieve *filter, int exec, address_item **generated)
3366 filter->pc = filter->filter;
3369 filter->require_envelope = 0;
3370 filter->require_fileinto = 0;
3371 #ifdef ENCODED_CHARACTER
3372 filter->require_encoded_character = FALSE;
3374 #ifdef ENVELOPE_AUTH
3375 filter->require_envelope_auth = 0;
3378 filter->require_enotify = 0;
3379 filter->notified = (struct Notification*)0;
3382 filter->require_subaddress = FALSE;
3385 filter->require_vacation = FALSE;
3386 filter->vacation_ran = 0; /*XXX missing init? */
3388 filter->require_copy = FALSE;
3389 filter->require_iascii_numeric = FALSE;
3391 if (parse_white(filter) == -1) return -1;
3393 if (exec && filter->vacation_directory && filter_test == FTEST_NONE)
3396 struct dirent *oncelog;
3397 struct stat properties;
3400 /* clean up old vacation log databases */
3402 if ( !(oncelogdir = exim_opendir(filter->vacation_directory))
3405 filter->errmsg = CUS "unable to open vacation directory";
3413 while ((oncelog = readdir(oncelogdir)))
3414 if (strlen(oncelog->d_name) == 32)
3416 uschar *s = string_sprintf("%s/%s", filter->vacation_directory, oncelog->d_name);
3417 if (Ustat(s, &properties) == 0 && properties.st_mtime+VACATION_MAX_DAYS*86400 < now)
3420 closedir(oncelogdir);
3424 while (parse_identifier(filter, CUS "require"))
3427 require-command = "require" <capabilities: string-list>
3433 if (parse_white(filter) == -1) return -1;
3434 if ((m = parse_stringlist(filter, &cap)) != 1)
3436 if (m == 0) filter->errmsg = CUS "capability string list expected";
3439 for (gstring * check = cap; check->s; ++check)
3441 if (eq_octet(check, &str_envelope, FALSE)) filter->require_envelope = 1;
3442 else if (eq_octet(check, &str_fileinto, FALSE)) filter->require_fileinto = 1;
3443 #ifdef ENCODED_CHARACTER
3444 else if (eq_octet(check, &str_encoded_character, FALSE)) filter->require_encoded_character = TRUE;
3446 #ifdef ENVELOPE_AUTH
3447 else if (eq_octet(check, &str_envelope_auth, FALSE)) filter->require_envelope_auth = 1;
3450 else if (eq_octet(check, &str_enotify, FALSE))
3452 if (!filter->enotify_mailto_owner)
3454 filter->errmsg = CUS "enotify disabled";
3457 filter->require_enotify = 1;
3461 else if (eq_octet(check, &str_subaddress, FALSE)) filter->require_subaddress = TRUE;
3464 else if (eq_octet(check, &str_vacation, FALSE))
3466 if (filter_test == FTEST_NONE && !filter->vacation_directory)
3468 filter->errmsg = CUS "vacation disabled";
3471 filter->require_vacation = TRUE;
3474 else if (eq_octet(check, &str_copy, FALSE)) filter->require_copy = TRUE;
3475 else if (eq_octet(check, &str_comparator_ioctet, FALSE)) ;
3476 else if (eq_octet(check, &str_comparator_iascii_casemap, FALSE)) ;
3477 else if (eq_octet(check, &str_comparator_enascii_casemap, FALSE)) ;
3478 else if (eq_octet(check, &str_comparator_iascii_numeric, FALSE)) filter->require_iascii_numeric = TRUE;
3481 filter->errmsg = CUS "unknown capability";
3485 if (parse_semicolon(filter) == -1) return -1;
3487 if (parse_commands(filter, exec, generated) == -1) return -1;
3490 filter->errmsg = CUS "syntax error";
3497 /*************************************************
3498 * Interpret a sieve filter file *
3499 *************************************************/
3503 filter points to the entire file, read into store as a single string
3504 options controls whether various special things are allowed, and requests
3505 special actions (not currently used)
3507 vacation_directory where to store vacation "once" files
3508 enotify_mailto_owner owner of mailto notifications
3509 useraddress string expression for :user part of address
3510 subaddress string expression for :subaddress part of address
3511 inbox string expression for "keep"
3512 generated where to hang newly-generated addresses
3513 error where to pass back an error text
3515 Returns: FF_DELIVERED success, a significant action was taken
3516 FF_NOTDELIVERED success, no significant action
3517 FF_DEFER defer requested
3518 FF_FAIL fail requested
3519 FF_FREEZE freeze requested
3520 FF_ERROR there was a problem
3524 sieve_interpret(const uschar * filter, int options, const sieve_block * sb,
3525 address_item ** generated, uschar ** error)
3531 DEBUG(D_route) debug_printf_indent("Sieve: start of processing\n");
3533 sieve.filter = filter;
3535 GET_OPTION("sieve_vacation_directory");
3536 if (!sb || !sb->vacation_dir)
3537 sieve.vacation_directory = NULL;
3538 else if (!(sieve.vacation_directory = expand_cstring(sb->vacation_dir)))
3540 *error = string_sprintf("failed to expand \"%s\" "
3541 "(sieve_vacation_directory): %s", sb->vacation_dir, expand_string_message);
3545 GET_OPTION("sieve_vacation_directory");
3546 if (!sb || !sb->inbox)
3547 sieve.inbox = US"inbox";
3548 else if (!(sieve.inbox = expand_cstring(sb->inbox)))
3550 *error = string_sprintf("failed to expand \"%s\" "
3551 "(sieve_inbox): %s", sb->inbox, expand_string_message);
3555 GET_OPTION("sieve_enotify_mailto_owner");
3556 if (!sb || !sb->enotify_mailto_owner)
3557 sieve.enotify_mailto_owner = NULL;
3558 else if (!(sieve.enotify_mailto_owner = expand_cstring(sb->enotify_mailto_owner)))
3560 *error = string_sprintf("failed to expand \"%s\" "
3561 "(sieve_enotify_mailto_owner): %s", sb->enotify_mailto_owner,
3562 expand_string_message);
3566 GET_OPTION("sieve_useraddress");
3567 sieve.useraddress = sb && sb->useraddress
3568 ? sb->useraddress : CUS "$local_part_prefix$local_part$local_part_suffix";
3569 GET_OPTION("sieve_subaddress");
3570 sieve.subaddress = sb ? sb->subaddress : NULL;
3572 #ifdef COMPILE_SYNTAX_CHECKER
3573 if (parse_start(&sieve, 0, generated) == 1)
3575 if (parse_start(&sieve, 1, generated) == 1)
3579 add_addr(generated, sieve.inbox, 1, 0, 0, 0);
3580 msg = US"Implicit keep";
3585 msg = US"No implicit keep";
3590 msg = string_sprintf("Sieve error: %s in line %d", sieve.errmsg, sieve.line);
3591 #ifdef COMPILE_SYNTAX_CHECKER
3595 add_addr(generated, sieve.inbox, 1, 0, 0, 0);
3600 #ifndef COMPILE_SYNTAX_CHECKER
3601 if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
3602 else debug_printf_indent("%s\n", msg);
3606 DEBUG(D_route) debug_printf_indent("Sieve: end of processing\n");
3611 /* Module API: print list of supported sieve extensions to given stream */
3613 sieve_extensions(FILE * fp)
3615 for (const uschar ** pp = exim_sieve_extension_list; *pp; ++pp)
3616 fprintf(fp, "%s\n", *pp);
3620 /******************************************************************************/
3623 static void * sieve_functions[] = {
3624 [SIEVE_INTERPRET] = sieve_interpret,
3625 [SIEVE_EXTENSIONS] = sieve_extensions,
3628 misc_module_info sieve_filter_module_info =
3630 .name = US"sieve_filter",
3632 .dyn_magic = MISC_MODULE_MAGIC,
3635 .functions = sieve_functions,
3636 .functions_count = nelem(sieve_functions),
3639 /* End of sieve_filter.c */