1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
6 * Copyright (c) The Exim Maintainers 2016 - 2023
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;
83 const uschar *vacation_directory;
84 const uschar *subaddress;
85 const uschar *useraddress;
87 BOOL require_iascii_numeric;
90 enum Comparator { COMP_OCTET, COMP_EN_ASCII_CASEMAP, COMP_ASCII_NUMERIC };
91 enum MatchType { MATCH_IS, MATCH_CONTAINS, MATCH_MATCHES };
93 enum AddressPart { ADDRPART_USER, ADDRPART_DETAIL, ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
95 enum AddressPart { ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
97 enum RelOp { LT, LE, EQ, GE, GT, NE };
103 struct Notification *next;
106 /* This should be a complete list of supported extensions, so that an external
107 ManageSieve (RFC 5804) program can interrogate the current Exim binary for the
108 list of extensions and provide correct information to a client.
110 We'll emit the list in the order given here; keep it alphabetically sorted, so
111 that callers don't get surprised.
113 List *MUST* end with a NULL. Which at least makes ifdef-vs-comma easier. */
115 const uschar *exim_sieve_extension_list[] = {
116 CUS"comparator-i;ascii-numeric",
118 #ifdef ENCODED_CHARACTER
119 CUS"encoded-character",
138 static int eq_asciicase(const gstring * needle, const gstring * haystack, BOOL match_prefix);
139 static int parse_test(struct Sieve *filter, int *cond, int exec);
140 static int parse_commands(struct Sieve *filter, int exec, address_item **generated);
142 static uschar str_from_c[] = "From";
143 static const gstring str_from = { .s = str_from_c, .ptr = 4, .size = 5 };
144 static uschar str_to_c[] = "To";
145 static const gstring str_to = { .s = str_to_c, .ptr = 2, .size = 3 };
146 static uschar str_cc_c[] = "Cc";
147 static const gstring str_cc = { .s = str_cc_c, .ptr = 2, .size = 3 };
148 static uschar str_bcc_c[] = "Bcc";
149 static const gstring str_bcc = { .s = str_bcc_c, .ptr = 3, .size = 4 };
151 static uschar str_auth_c[] = "auth";
152 static const gstring str_auth = { .s = str_auth_c, .ptr = 4, .size = 5 };
154 static uschar str_sender_c[] = "Sender";
155 static const gstring str_sender = { .s = str_sender_c, .ptr = 6, .size = 7 };
156 static uschar str_resent_from_c[] = "Resent-From";
157 static const gstring str_resent_from = { .s = str_resent_from_c, .ptr = 11, .size = 12 };
158 static uschar str_resent_to_c[] = "Resent-To";
159 static const gstring str_resent_to = { .s = str_resent_to_c, .ptr = 9, .size = 10 };
160 static uschar str_fileinto_c[] = "fileinto";
161 static const gstring str_fileinto = { .s = str_fileinto_c, .ptr = 8, .size = 9 };
162 static uschar str_envelope_c[] = "envelope";
163 static const gstring str_envelope = { .s = str_envelope_c, .ptr = 8, .size = 9 };
164 #ifdef ENCODED_CHARACTER
165 static uschar str_encoded_character_c[] = "encoded-character";
166 static const gstring str_encoded_character = { .s = str_encoded_character_c, .ptr = 17, .size = 18 };
169 static uschar str_envelope_auth_c[] = "envelope-auth";
170 static const gstring str_envelope_auth = { .s = str_envelope_auth_c, .ptr = 13, .size = 14 };
173 static uschar str_enotify_c[] = "enotify";
174 static const gstring str_enotify = { .s = str_enotify_c, .ptr = 7, .size = 8 };
175 static uschar str_online_c[] = "online";
176 static const gstring str_online = { .s = str_online_c, .ptr = 6, .size = 7 };
177 static uschar str_maybe_c[] = "maybe";
178 static const gstring str_maybe = { .s = str_maybe_c, .ptr = 5, .size = 6 };
179 static uschar str_auto_submitted_c[] = "Auto-Submitted";
180 static const gstring str_auto_submitted = { .s = str_auto_submitted_c, .ptr = 14, .size = 15 };
183 static uschar str_subaddress_c[] = "subaddress";
184 static const gstring str_subaddress = { .s = str_subaddress_c, .ptr = 10, .size = 11 };
187 static uschar str_vacation_c[] = "vacation";
188 static const gstring str_vacation = { .s = str_vacation_c, .ptr = 8, .size = 9 };
189 static uschar str_subject_c[] = "Subject";
190 static const gstring str_subject = { .s = str_subject_c, .ptr = 7, .size = 8 };
192 static uschar str_copy_c[] = "copy";
193 static const gstring str_copy = { .s = str_copy_c, .ptr = 4, .size = 5 };
194 static uschar str_iascii_casemap_c[] = "i;ascii-casemap";
195 static const gstring str_iascii_casemap = { .s = str_iascii_casemap_c, .ptr = 15, .size = 16 };
196 static uschar str_enascii_casemap_c[] = "en;ascii-casemap";
197 static const gstring str_enascii_casemap = { .s = str_enascii_casemap_c, .ptr = 16, .size = 17 };
198 static uschar str_ioctet_c[] = "i;octet";
199 static const gstring str_ioctet = { .s = str_ioctet_c, .ptr = 7, .size = 8 };
200 static uschar str_iascii_numeric_c[] = "i;ascii-numeric";
201 static const gstring str_iascii_numeric = { .s = str_iascii_numeric_c, .ptr = 15, .size = 16 };
202 static uschar str_comparator_iascii_casemap_c[] = "comparator-i;ascii-casemap";
203 static const gstring str_comparator_iascii_casemap = { .s = str_comparator_iascii_casemap_c, .ptr = 26, .size = 27 };
204 static uschar str_comparator_enascii_casemap_c[] = "comparator-en;ascii-casemap";
205 static const gstring str_comparator_enascii_casemap = { .s = str_comparator_enascii_casemap_c, .ptr = 27, .size = 28 };
206 static uschar str_comparator_ioctet_c[] = "comparator-i;octet";
207 static const gstring str_comparator_ioctet = { .s = str_comparator_ioctet_c, .ptr = 18, .size = 19 };
208 static uschar str_comparator_iascii_numeric_c[] = "comparator-i;ascii-numeric";
209 static const gstring str_comparator_iascii_numeric = { .s = str_comparator_iascii_numeric_c, .ptr = 26, .size = 27 };
212 /*************************************************
213 * Encode to quoted-printable *
214 *************************************************/
221 dst, allocated, a US-ASCII string
225 quoted_printable_encode(const gstring * src)
227 gstring * dst = NULL;
231 for (const uschar * start = src->s, * end = start + src->ptr;
232 start < end; ++start)
235 if (line >= 73) /* line length limit */
237 dst = string_catn(dst, US"=\n", 2); /* line split */
240 if ( (ch >= '!' && ch <= '<')
241 || (ch >= '>' && ch <= '~')
242 || ( (ch == '\t' || ch == ' ')
243 && start+2 < end && (start[1] != '\r' || start[2] != '\n') /* CRLF */
247 dst = string_catn(dst, start, 1); /* copy char */
250 else if (ch == '\r' && start+1 < end && start[1] == '\n') /* CRLF */
252 dst = string_catn(dst, US"\n", 1); /* NL */
254 ++start; /* consume extra input char */
258 dst = string_fmt_append(dst, "=%02X", ch);
263 (void) string_from_gstring(dst);
264 gstring_release_unused(dst);
269 /*************************************************
270 * Check mail address for correct syntax *
271 *************************************************/
274 Check mail address for being syntactically correct.
277 filter points to the Sieve filter including its state
278 address String containing one address
281 1 Mail address is syntactically OK
286 check_mail_address(struct Sieve * filter, const gstring * address)
288 int start, end, domain;
289 uschar * error, * ss;
291 if (address->ptr > 0)
293 ss = parse_extract_address(address->s, &error, &start, &end, &domain,
297 filter->errmsg = string_sprintf("malformed address \"%s\" (%s)",
306 filter->errmsg = CUS "empty address";
312 /*************************************************
313 * Decode URI encoded string *
314 *************************************************/
318 str URI encoded string
321 str is modified in place
322 TRUE Decoding successful
328 uri_decode(gstring * str)
332 if (str->ptr == 0) return TRUE;
333 for (t = s = str->s, e = s + str->ptr; s < e; )
336 if (s+2 < e && isxdigit(s[1]) && isxdigit(s[2]))
338 *t++ = ((isdigit(s[1]) ? s[1]-'0' : tolower(s[1])-'a'+10)<<4)
339 | (isdigit(s[2]) ? s[2]-'0' : tolower(s[2])-'a'+10);
348 str->ptr = t - str->s;
353 /*************************************************
355 *************************************************/
360 mailtoURI = "mailto:" [ to ] [ headers ]
361 to = [ addr-spec *("%2C" addr-spec ) ]
362 headers = "?" header *( "&" header )
363 header = hname " = " hvalue
368 filter points to the Sieve filter including its state
369 uri URI, excluding scheme
370 recipient list of recipients; prepnded to
374 1 URI is syntactically OK
380 parse_mailto_uri(struct Sieve * filter, const uschar * uri,
381 string_item ** recipient, gstring * header, gstring * subject,
384 const uschar * start;
386 if (Ustrncmp(uri, "mailto:", 7))
388 filter->errmsg = US "Unknown URI scheme";
393 if (*uri && *uri != '?')
397 for (start = uri; *uri && *uri != '?' && (*uri != '%' || uri[1] != '2' || tolower(uri[2]) != 'c'); ++uri);
400 gstring * to = string_catn(NULL, start, uri - start);
405 filter->errmsg = US"Invalid URI encoding";
408 new = store_get(sizeof(string_item), GET_UNTAINTED);
409 new->text = string_from_gstring(to);
410 new->next = *recipient;
415 filter->errmsg = US"Missing addr-spec in URI";
418 if (*uri == '%') uri += 3;
424 gstring * hname = string_get(0), * hvalue = NULL;
427 for (start = uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(), %", *uri)); ++uri) ;
430 hname = string_catn(hname, start, uri-start);
432 if (!uri_decode(hname))
434 filter->errmsg = US"Invalid URI encoding";
441 filter->errmsg = US"Missing equal after hname";
446 for (start = uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(), %", *uri)); ++uri) ;
449 hvalue = string_catn(NULL, start, uri-start); /*XXX this used to say "hname =" */
451 if (!uri_decode(hvalue))
453 filter->errmsg = US"Invalid URI encoding";
457 if (hname->ptr == 2 && strcmpic(hname->s, US"to") == 0)
459 string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
460 new->text = string_from_gstring(hvalue);
461 new->next = *recipient;
464 else if (hname->ptr == 4 && strcmpic(hname->s, US"body") == 0)
466 else if (hname->ptr == 7 && strcmpic(hname->s, US"subject") == 0)
470 static gstring ignore[] =
472 {.s = US"date", .ptr = 4, .size = 5},
473 {.s = US"from", .ptr = 4, .size = 5},
474 {.s = US"message-id", .ptr = 10, .size = 11},
475 {.s = US"received", .ptr = 8, .size = 9},
476 {.s = US"auto-submitted", .ptr = 14, .size = 15}
478 static gstring * end = ignore + nelem(ignore);
481 for (i = ignore; i < end && !eq_asciicase(hname, i, FALSE); ++i);
484 hname = string_fmt_append(NULL, "%Y%Y: %Y\n", header, hname, hvalue);
485 (void) string_from_gstring(hname);
486 /*XXX we seem to do nothing with this new hname? */
489 if (*uri == '&') ++uri;
494 filter->errmsg = US"Syntactically invalid URI";
502 /*************************************************
503 * Octet-wise string comparison *
504 *************************************************/
508 needle UTF-8 string to search ...
509 haystack ... inside the haystack
510 match_prefix TRUE to compare if needle is a prefix of haystack
512 Returns: 0 needle not found in haystack
517 eq_octet(const gstring *needle, const gstring *haystack, BOOL match_prefix)
529 if (*n & 0x80) return 0;
530 if (*h & 0x80) return 0;
532 if (*n != *h) return 0;
538 return (match_prefix ? nl == 0 : nl == 0 && hl == 0);
542 /*************************************************
543 * ASCII case-insensitive string comparison *
544 *************************************************/
548 needle UTF-8 string to search ...
549 haystack ... inside the haystack
550 match_prefix TRUE to compare if needle is a prefix of haystack
552 Returns: 0 needle not found in haystack
557 eq_asciicase(const gstring *needle, const gstring *haystack, BOOL match_prefix)
567 while (nl > 0 && hl > 0)
572 if (nc & 0x80) return 0;
573 if (hc & 0x80) return 0;
575 /* tolower depends on the locale and only ASCII case must be insensitive */
576 if ((nc >= 'A' && nc <= 'Z' ? nc | 0x20 : nc) != (hc >= 'A' && hc <= 'Z' ? hc | 0x20 : hc)) return 0;
582 return (match_prefix ? nl == 0 : nl == 0 && hl == 0);
586 /*************************************************
587 * Glob pattern search *
588 *************************************************/
592 needle pattern to search ...
593 haystack ... inside the haystack
594 ascii_caseless ignore ASCII case
595 match_octet match octets, not UTF-8 multi-octet characters
597 Returns: 0 needle not found in haystack
603 eq_glob(const gstring *needle,
604 const gstring *haystack, BOOL ascii_caseless, BOOL match_octet)
606 const uschar *n, *h, *nend, *hend;
611 nend = n+needle->ptr;
612 hend = h+haystack->ptr;
621 const uschar *npart, *hpart;
623 /* Try to match a non-star part of the needle at the current */
624 /* position in the haystack. */
628 while (npart<nend && *npart != '*') switch (*npart)
632 if (hpart == hend) return 0;
637 /* Match one UTF8 encoded character */
638 if ((*hpart&0xc0) == 0xc0)
641 while (hpart<hend && ((*hpart&0xc0) == 0x80)) ++hpart;
652 if (npart == nend) return -1;
657 if (hpart == hend) return 0;
658 /* tolower depends on the locale, but we need ASCII */
662 (*hpart&0x80) || (*npart&0x80) ||
665 ? ((*npart>= 'A' && *npart<= 'Z' ? *npart|0x20 : *npart) != (*hpart>= 'A' && *hpart<= 'Z' ? *hpart|0x20 : *hpart))
670 /* string match after a star failed, advance and try again */
684 /* at this point, a part was matched successfully */
685 if (may_advance && npart == nend && hpart<hend)
686 /* needle ends, but haystack does not: if there was a star before, advance and try again */
695 return (h == hend ? 1 : may_advance);
699 /*************************************************
700 * ASCII numeric comparison *
701 *************************************************/
705 a first numeric string
706 b second numeric string
707 relop relational operator
709 Returns: 0 not (a relop b)
714 eq_asciinumeric(const gstring *a, const gstring *b, enum RelOp relop)
717 const uschar *as, *aend, *bs, *bend;
725 while (*as>= '0' && *as<= '9' && as<aend) ++as;
727 while (*bs>= '0' && *bs<= '9' && bs<bend) ++bs;
730 if (al && bl == 0) cmp = -1;
731 else if (al == 0 && bl == 0) cmp = 0;
732 else if (al == 0 && bl) cmp = 1;
736 if (cmp == 0) cmp = memcmp(a->s, b->s, al);
740 case LT: return cmp < 0;
741 case LE: return cmp <= 0;
742 case EQ: return cmp == 0;
743 case GE: return cmp >= 0;
744 case GT: return cmp > 0;
745 case NE: return cmp != 0;
752 /*************************************************
754 *************************************************/
758 filter points to the Sieve filter including its state
759 needle UTF-8 pattern or string to search ...
760 haystack ... inside the haystack
764 Returns: 0 needle not found in haystack
766 -1 comparator does not offer matchtype
770 compare(struct Sieve * filter, const gstring * needle, const gstring * haystack,
771 enum Comparator co, enum MatchType mt)
775 if ( (filter_test != FTEST_NONE && debug_selector != 0)
776 || (debug_selector & D_filter) != 0)
778 debug_printf_indent("String comparison (match ");
781 case MATCH_IS: debug_printf_indent(":is"); break;
782 case MATCH_CONTAINS: debug_printf_indent(":contains"); break;
783 case MATCH_MATCHES: debug_printf_indent(":matches"); break;
785 debug_printf_indent(", comparison \"");
788 case COMP_OCTET: debug_printf_indent("i;octet"); break;
789 case COMP_EN_ASCII_CASEMAP: debug_printf_indent("en;ascii-casemap"); break;
790 case COMP_ASCII_NUMERIC: debug_printf_indent("i;ascii-numeric"); break;
792 debug_printf_indent("\"):\n");
793 debug_printf_indent(" Search = %s (%d chars)\n", needle->s, needle->ptr);
794 debug_printf_indent(" Inside = %s (%d chars)\n", haystack->s, haystack->ptr);
802 if (eq_octet(needle, haystack, FALSE)) r = 1;
804 case COMP_EN_ASCII_CASEMAP:
805 if (eq_asciicase(needle, haystack, FALSE)) r = 1;
807 case COMP_ASCII_NUMERIC:
808 if (!filter->require_iascii_numeric)
810 filter->errmsg = CUS "missing previous require \"comparator-i;ascii-numeric\";";
813 if (eq_asciinumeric(needle, haystack, EQ)) r = 1;
825 for (h = *haystack; h.ptr; ++h.s, --h.ptr)
826 if (eq_octet(needle, &h, TRUE)) { r = 1; break; }
828 case COMP_EN_ASCII_CASEMAP:
829 for (h = *haystack; h.ptr; ++h.s, --h.ptr)
830 if (eq_asciicase(needle, &h, TRUE)) { r = 1; break; }
833 filter->errmsg = CUS "comparator does not offer specified matchtype";
843 if ((r = eq_glob(needle, haystack, FALSE, TRUE)) == -1)
845 filter->errmsg = CUS "syntactically invalid pattern";
849 case COMP_EN_ASCII_CASEMAP:
850 if ((r = eq_glob(needle, haystack, TRUE, TRUE)) == -1)
852 filter->errmsg = CUS "syntactically invalid pattern";
857 filter->errmsg = CUS "comparator does not offer specified matchtype";
862 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
863 (debug_selector & D_filter) != 0)
864 debug_printf_indent(" Result %s\n", r?"true":"false");
869 /*************************************************
870 * Check header field syntax *
871 *************************************************/
874 RFC 2822, section 3.6.8 says:
878 ftext = %d33-57 / ; Any character except
879 %d59-126 ; controls, SP, and
882 That forbids 8-bit header fields. This implementation accepts them, since
883 all of Exim is 8-bit clean, so it adds %d128-%d255.
886 header header field to quote for suitable use in Exim expansions
888 Returns: 0 string is not a valid header field
889 1 string is a value header field
893 is_header(const gstring *header)
900 if (l == 0) return 0;
903 if (*h < 33 || *h == ':' || *h == 127)
912 /*************************************************
913 * Quote special characters string *
914 *************************************************/
918 header header field to quote for suitable use in Exim expansions
921 Returns: quoted string
924 static const uschar *
925 quote(const gstring * header)
927 gstring * quoted = NULL;
931 for (l = header->ptr, h = header->s; l; ++h, --l)
935 quoted = string_catn(quoted, CUS "\\0", 2);
940 quoted = string_catn(quoted, CUS "\\", 1);
942 quoted = string_catn(quoted, h, 1);
945 return string_from_gstring(quoted);
949 /*************************************************
950 * Add address to list of generated addresses *
951 *************************************************/
954 According to RFC 5228, duplicate delivery to the same address must
955 not happen, so the list is first searched for the address.
958 generated list of generated addresses
959 addr new address to add
960 file address denotes a file
966 add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
968 address_item *new_addr;
970 for (new_addr = *generated; new_addr; new_addr = new_addr->next)
971 if ( Ustrcmp(new_addr->address, addr) == 0
973 || testflag(new_addr, af_pfr)
974 || testflag(new_addr, af_file)
978 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
979 debug_printf_indent("Repeated %s `%s' ignored.\n", file ? "fileinto" : "redirect", addr);
984 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
985 debug_printf_indent("%s `%s'\n", file ? "fileinto" : "redirect", addr);
987 new_addr = deliver_make_addr(addr, TRUE);
990 setflag(new_addr, af_pfr);
991 setflag(new_addr, af_file);
994 new_addr->prop.errors_address = NULL;
995 new_addr->next = *generated;
996 *generated = new_addr;
1000 /*************************************************
1001 * Return decoded header field *
1002 *************************************************/
1005 Unfold the header field as described in RFC 2822 and remove all
1006 leading and trailing white space, then perform MIME decoding and
1007 translate the header field to UTF-8.
1010 value returned value of the field
1011 header name of the header field
1013 Returns: nothing The expanded string is empty
1014 in case there is no such header
1018 expand_header(gstring * value, const gstring * header)
1024 value->s = (uschar*)0;
1026 t = r = s = expand_string(string_sprintf("$rheader_%s", quote(header)));
1028 while (*r == ' ' || *r == '\t') ++r;
1035 while (t>s && (*(t-1) == ' ' || *(t-1) == '\t')) --t;
1037 value->s = rfc2047_decode(s, check_rfc2047_length, US"utf-8", '\0', &value->ptr, &errmsg);
1041 /*************************************************
1042 * Parse remaining hash comment *
1043 *************************************************/
1047 Comment up to terminating CRLF
1050 filter points to the Sieve filter including its state
1057 parse_hashcomment(struct Sieve * filter)
1063 if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
1065 if (*filter->pc == '\n')
1078 filter->errmsg = CUS "missing end of comment";
1083 /*************************************************
1084 * Parse remaining C-style comment *
1085 *************************************************/
1089 Everything up to star slash
1092 filter points to the Sieve filter including its state
1099 parse_comment(struct Sieve *filter)
1103 if (*filter->pc == '*' && (filter->pc)[1] == '/')
1111 filter->errmsg = CUS "missing end of comment";
1116 /*************************************************
1117 * Parse optional white space *
1118 *************************************************/
1122 Spaces, tabs, CRLFs, hash comments or C-style comments
1125 filter points to the Sieve filter including its state
1132 parse_white(struct Sieve *filter)
1136 if (*filter->pc == ' ' || *filter->pc == '\t') ++filter->pc;
1138 else if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
1140 else if (*filter->pc == '\n')
1150 else if (*filter->pc == '#')
1152 if (parse_hashcomment(filter) == -1) return -1;
1154 else if (*filter->pc == '/' && (filter->pc)[1] == '*')
1156 if (parse_comment(filter) == -1) return -1;
1164 #ifdef ENCODED_CHARACTER
1165 /*************************************************
1166 * Decode hex-encoded-character string *
1167 *************************************************/
1170 Encoding definition:
1171 blank = SP / TAB / CRLF
1172 hex-pair-seq = *blank hex-pair *(1*blank hex-pair) *blank
1173 hex-pair = 1*2HEXDIG
1176 src points to a hex-pair-seq
1177 end points to its end
1178 dst points to the destination of the decoded octets,
1179 optionally to (uschar*)0 for checking only
1181 Returns: >= 0 number of decoded octets
1186 hex_decode(uschar *src, uschar *end, uschar *dst)
1190 while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
1196 d<2 && src<end && isxdigit(n = tolower(*src));
1197 x = (x<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')) , ++d, ++src) ;
1198 if (d == 0) return -1;
1199 if (dst) *dst++ = x;
1201 if (src == end) return decoded;
1202 if (*src == ' ' || *src == '\t' || *src == '\n')
1203 while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
1212 /*************************************************
1213 * Decode unicode-encoded-character string *
1214 *************************************************/
1217 Encoding definition:
1218 blank = SP / TAB / CRLF
1219 unicode-hex-seq = *blank unicode-hex *(blank unicode-hex) *blank
1220 unicode-hex = 1*HEXDIG
1222 It is an error for a script to use a hexadecimal value that isn't in
1223 either the range 0 to D7FF or the range E000 to 10FFFF.
1225 At this time, strings are already scanned, thus the CRLF is converted
1226 to the internally used \n (should RFC_EOL have been used).
1229 src points to a unicode-hex-seq
1230 end points to its end
1231 dst points to the destination of the decoded octets,
1232 optionally to (uschar*)0 for checking only
1234 Returns: >= 0 number of decoded octets
1236 -2 semantic error (character range violation)
1240 unicode_decode(uschar *src, uschar *end, uschar *dst)
1244 while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
1251 for (hex_seq = src; src < end && *src == '0'; ) src++;
1253 d < 7 && src < end && isxdigit(n = tolower(*src));
1254 c = (c<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')), ++d, ++src) ;
1255 if (src == hex_seq) return -1;
1256 if (d == 7 || (!((c >= 0 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0x10ffff)))) return -2;
1259 if (dst) *dst++ = c;
1262 else if (c>= 0x80 && c<= 0x7ff)
1266 *dst++ = 192+(c>>6);
1267 *dst++ = 128+(c&0x3f);
1271 else if (c>= 0x800 && c<= 0xffff)
1275 *dst++ = 224+(c>>12);
1276 *dst++ = 128+((c>>6)&0x3f);
1277 *dst++ = 128+(c&0x3f);
1281 else if (c>= 0x10000 && c<= 0x1fffff)
1285 *dst++ = 240+(c>>18);
1286 *dst++ = 128+((c>>10)&0x3f);
1287 *dst++ = 128+((c>>6)&0x3f);
1288 *dst++ = 128+(c&0x3f);
1292 if (*src == ' ' || *src == '\t' || *src == '\n')
1294 while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
1295 if (src == end) return decoded;
1304 /*************************************************
1305 * Decode encoded-character string *
1306 *************************************************/
1309 Encoding definition:
1310 encoded-arb-octets = "${hex:" hex-pair-seq "}"
1311 encoded-unicode-char = "${unicode:" unicode-hex-seq "}"
1314 encoded points to an encoded string, returns decoded string
1315 filter points to the Sieve filter including its state
1322 string_decode(struct Sieve *filter, gstring *data)
1324 uschar *src, *dst, *end;
1328 end = data->s+data->ptr;
1334 strncmpic(src, US "${hex:", 6) == 0
1335 && (brace = Ustrchr(src+6, '}')) != (uschar*)0
1336 && (hex_decode(src+6, brace, (uschar*)0))>= 0
1339 dst += hex_decode(src+6, brace, dst);
1343 strncmpic(src, US "${unicode:", 10) == 0
1344 && (brace = Ustrchr(src+10, '}')) != (uschar*)0
1347 switch (unicode_decode(src+10, brace, (uschar*)0))
1351 filter->errmsg = CUS "unicode character out of range";
1361 dst += unicode_decode(src+10, brace, dst);
1366 else *dst++ = *src++;
1368 data->ptr = dst-data->s;
1375 /*************************************************
1376 * Parse an optional string *
1377 *************************************************/
1381 quoted-string = DQUOTE *CHAR DQUOTE
1382 ;; in general, \ CHAR inside a string maps to CHAR
1383 ;; so \" maps to " and \\ maps to \
1384 ;; note that newlines and other characters are all allowed
1387 multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
1388 *(multi-line-literal / multi-line-dotstuff)
1390 multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
1391 multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
1392 ;; A line containing only "." ends the multi-line.
1393 ;; Remove a leading '.' if followed by another '.'.
1394 string = quoted-string / multi-line
1397 filter points to the Sieve filter including its state
1398 id specifies identifier to match
1402 0 identifier not matched
1406 parse_string(struct Sieve *filter, gstring *data)
1413 if (*filter->pc == '"') /* quoted string */
1418 if (*filter->pc == '"') /* end of string */
1423 data->ptr = len_string_from_gstring(g, &data->s);
1426 /* that way, there will be at least one character allocated */
1428 #ifdef ENCODED_CHARACTER
1429 if ( filter->require_encoded_character
1430 && string_decode(filter, data) == -1)
1435 else if (*filter->pc == '\\' && (filter->pc)[1]) /* quoted character */
1437 g = string_catn(g, filter->pc+1, 1);
1440 else /* regular character */
1443 if (*filter->pc == '\r' && (filter->pc)[1] == '\n') ++filter->line;
1445 if (*filter->pc == '\n')
1447 g = string_catn(g, US"\r", 1);
1451 g = string_catn(g, filter->pc, 1);
1455 filter->errmsg = CUS "missing end of string";
1458 else if (Ustrncmp(filter->pc, CUS "text:", 5) == 0) /* multiline string */
1461 /* skip optional white space followed by hashed comment or CRLF */
1462 while (*filter->pc == ' ' || *filter->pc == '\t') ++filter->pc;
1463 if (*filter->pc == '#')
1465 if (parse_hashcomment(filter) == -1) return -1;
1468 else if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
1470 else if (*filter->pc == '\n')
1482 filter->errmsg = CUS "syntax error";
1488 if (*filter->pc == '\r' && (filter->pc)[1] == '\n') /* end of line */
1490 if (*filter->pc == '\n') /* end of line */
1493 g = string_catn(g, CUS "\r\n", 2);
1501 if (*filter->pc == '.' && (filter->pc)[1] == '\r' && (filter->pc)[2] == '\n') /* end of string */
1503 if (*filter->pc == '.' && (filter->pc)[1] == '\n') /* end of string */
1507 data->ptr = len_string_from_gstring(g, &data->s);
1510 /* that way, there will be at least one character allocated */
1518 #ifdef ENCODED_CHARACTER
1519 if ( filter->require_encoded_character
1520 && string_decode(filter, data) == -1)
1525 else if (*filter->pc == '.' && (filter->pc)[1] == '.') /* remove dot stuffing */
1527 g = string_catn(g, CUS ".", 1);
1531 else /* regular character */
1533 g = string_catn(g, filter->pc, 1);
1537 filter->errmsg = CUS "missing end of multi line string";
1544 /*************************************************
1545 * Parse a specific identifier *
1546 *************************************************/
1550 identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
1553 filter points to the Sieve filter including its state
1554 id specifies identifier to match
1557 0 identifier not matched
1561 parse_identifier(struct Sieve *filter, const uschar *id)
1563 size_t idlen = Ustrlen(id);
1565 if (strncmpic(US filter->pc, US id, idlen) == 0)
1567 uschar next = filter->pc[idlen];
1569 if ((next>= 'A' && next<= 'Z') || (next>= 'a' && next<= 'z') || next == '_' || (next>= '0' && next<= '9')) return 0;
1570 filter->pc += idlen;
1577 /*************************************************
1579 *************************************************/
1583 number = 1*DIGIT [QUANTIFIER]
1584 QUANTIFIER = "K" / "M" / "G"
1587 filter points to the Sieve filter including its state
1591 -1 no string list found
1595 parse_number(struct Sieve *filter, unsigned long *data)
1599 if (*filter->pc>= '0' && *filter->pc<= '9')
1604 d = Ustrtoul(filter->pc, &e, 10);
1605 if (errno == ERANGE)
1607 filter->errmsg = CUstrerror(ERANGE);
1612 if (*filter->pc == 'K') { u = 1024; ++filter->pc; }
1613 else if (*filter->pc == 'M') { u = 1024*1024; ++filter->pc; }
1614 else if (*filter->pc == 'G') { u = 1024*1024*1024; ++filter->pc; }
1615 if (d>(ULONG_MAX/u))
1617 filter->errmsg = CUstrerror(ERANGE);
1626 filter->errmsg = CUS "missing number";
1632 /*************************************************
1633 * Parse a string list *
1634 *************************************************/
1638 string-list = "[" string *(", " string) "]" / string
1641 filter points to the Sieve filter including its state
1642 data returns string list
1645 -1 no string list found
1649 parse_stringlist(struct Sieve *filter, gstring **data)
1651 const uschar *orig = filter->pc;
1652 int dataCapacity = 0;
1657 if (*filter->pc == '[') /* string list */
1662 if (parse_white(filter) == -1) goto error;
1663 if (dataLength+1 >= dataCapacity) /* increase buffer */
1667 dataCapacity = dataCapacity ? dataCapacity * 2 : 4;
1668 new = store_get(sizeof(gstring) * dataCapacity, GET_UNTAINTED);
1670 if (d) memcpy(new, d, sizeof(gstring)*dataLength);
1674 m = parse_string(filter, &d[dataLength]);
1677 if (dataLength == 0) break;
1680 filter->errmsg = CUS "missing string";
1684 else if (m == -1) goto error;
1686 if (parse_white(filter) == -1) goto error;
1687 if (*filter->pc == ',') ++filter->pc;
1690 if (*filter->pc == ']')
1692 d[dataLength].s = (uschar*)0;
1693 d[dataLength].ptr = -1;
1700 filter->errmsg = CUS "missing closing bracket";
1704 else /* single string */
1706 if (!(d = store_get(sizeof(gstring)*2, GET_UNTAINTED)))
1709 m = parse_string(filter, &d[0]);
1720 d[1].s = (uschar*)0;
1727 filter->errmsg = CUS "missing string list";
1732 /*************************************************
1733 * Parse an optional address part specifier *
1734 *************************************************/
1738 address-part = ":localpart" / ":domain" / ":all"
1739 address-part = / ":user" / ":detail"
1742 filter points to the Sieve filter including its state
1743 a returns address part specified
1746 0 no comparator found
1751 parse_addresspart(struct Sieve *filter, enum AddressPart *a)
1754 if (parse_identifier(filter, CUS ":user") == 1)
1756 if (!filter->require_subaddress)
1758 filter->errmsg = CUS "missing previous require \"subaddress\";";
1764 else if (parse_identifier(filter, CUS ":detail") == 1)
1766 if (!filter->require_subaddress)
1768 filter->errmsg = CUS "missing previous require \"subaddress\";";
1771 *a = ADDRPART_DETAIL;
1776 if (parse_identifier(filter, CUS ":localpart") == 1)
1778 *a = ADDRPART_LOCALPART;
1781 else if (parse_identifier(filter, CUS ":domain") == 1)
1783 *a = ADDRPART_DOMAIN;
1786 else if (parse_identifier(filter, CUS ":all") == 1)
1795 /*************************************************
1796 * Parse an optional comparator *
1797 *************************************************/
1801 comparator = ":comparator" <comparator-name: string>
1804 filter points to the Sieve filter including its state
1805 c returns comparator
1808 0 no comparator found
1809 -1 incomplete comparator found
1813 parse_comparator(struct Sieve *filter, enum Comparator *c)
1815 gstring comparator_name;
1817 if (parse_identifier(filter, CUS ":comparator") == 0) return 0;
1818 if (parse_white(filter) == -1) return -1;
1819 switch (parse_string(filter, &comparator_name))
1824 filter->errmsg = CUS "missing comparator";
1831 if (eq_asciicase(&comparator_name, &str_ioctet, FALSE))
1836 else if (eq_asciicase(&comparator_name, &str_iascii_casemap, FALSE))
1838 *c = COMP_EN_ASCII_CASEMAP;
1841 else if (eq_asciicase(&comparator_name, &str_enascii_casemap, FALSE))
1843 *c = COMP_EN_ASCII_CASEMAP;
1846 else if (eq_asciicase(&comparator_name, &str_iascii_numeric, FALSE))
1848 *c = COMP_ASCII_NUMERIC;
1853 filter->errmsg = CUS "invalid comparator";
1862 /*************************************************
1863 * Parse an optional match type *
1864 *************************************************/
1868 match-type = ":is" / ":contains" / ":matches"
1871 filter points to the Sieve filter including its state
1872 m returns match type
1875 0 no match type found
1879 parse_matchtype(struct Sieve *filter, enum MatchType *m)
1881 if (parse_identifier(filter, CUS ":is") == 1)
1886 else if (parse_identifier(filter, CUS ":contains") == 1)
1888 *m = MATCH_CONTAINS;
1891 else if (parse_identifier(filter, CUS ":matches") == 1)
1900 /*************************************************
1901 * Parse and interpret an optional test list *
1902 *************************************************/
1906 test-list = "(" test *("," test) ")"
1909 filter points to the Sieve filter including its state
1910 n total number of tests
1911 num_true number of passed tests
1912 exec Execute parsed statements
1915 0 no test list found
1916 -1 syntax or execution error
1920 parse_testlist(struct Sieve *filter, int *n, int *num_true, int exec)
1922 if (parse_white(filter) == -1) return -1;
1923 if (*filter->pc == '(')
1932 switch (parse_test(filter, &cond, exec))
1935 case 0: filter->errmsg = CUS "missing test"; return -1;
1936 default: ++*n; if (cond) ++*num_true; break;
1938 if (parse_white(filter) == -1) return -1;
1939 if (*filter->pc == ',') ++filter->pc;
1942 if (*filter->pc == ')')
1949 filter->errmsg = CUS "missing closing paren";
1957 /*************************************************
1958 * Parse and interpret an optional test *
1959 *************************************************/
1963 filter points to the Sieve filter including its state
1964 cond returned condition status
1965 exec Execute parsed statements
1969 -1 syntax or execution error
1973 parse_test(struct Sieve *filter, int *cond, int exec)
1975 if (parse_white(filter) == -1) return -1;
1976 if (parse_identifier(filter, CUS "address"))
1979 address-test = "address" { [address-part] [comparator] [match-type] }
1980 <header-list: string-list> <key-list: string-list>
1982 header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
1985 enum AddressPart addressPart = ADDRPART_ALL;
1986 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
1987 enum MatchType matchType = MATCH_IS;
1990 int ap = 0, co = 0, mt = 0;
1994 if (parse_white(filter) == -1) return -1;
1995 if ((m = parse_addresspart(filter, &addressPart)) != 0)
1997 if (m == -1) return -1;
2000 filter->errmsg = CUS "address part already specified";
2005 else if ((m = parse_comparator(filter, &comparator)) != 0)
2007 if (m == -1) return -1;
2010 filter->errmsg = CUS "comparator already specified";
2015 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2017 if (m == -1) return -1;
2020 filter->errmsg = CUS "match type already specified";
2027 if (parse_white(filter) == -1)
2029 if ((m = parse_stringlist(filter, &hdr)) != 1)
2031 if (m == 0) filter->errmsg = CUS "header string list expected";
2034 if (parse_white(filter) == -1)
2036 if ((m = parse_stringlist(filter, &key)) != 1)
2038 if (m == 0) filter->errmsg = CUS "key string list expected";
2042 for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
2044 uschar * header_value = NULL, * extracted_addr, * end_addr;
2046 if ( !eq_asciicase(h, &str_from, FALSE)
2047 && !eq_asciicase(h, &str_to, FALSE)
2048 && !eq_asciicase(h, &str_cc, FALSE)
2049 && !eq_asciicase(h, &str_bcc, FALSE)
2050 && !eq_asciicase(h, &str_sender, FALSE)
2051 && !eq_asciicase(h, &str_resent_from, FALSE)
2052 && !eq_asciicase(h, &str_resent_to, FALSE)
2055 filter->errmsg = CUS "invalid header field";
2060 /* We are only interested in addresses below, so no MIME decoding */
2061 if (!(header_value = expand_string(string_sprintf("$rheader_%s", quote(h)))))
2063 filter->errmsg = CUS "header string expansion failed";
2066 f.parse_allow_group = TRUE;
2067 while (*header_value && !*cond)
2070 int start, end, domain;
2072 uschar *part = NULL;
2074 end_addr = parse_find_address_end(header_value, FALSE);
2075 saveend = *end_addr;
2077 extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
2079 if (extracted_addr) switch (addressPart)
2081 case ADDRPART_ALL: part = extracted_addr; break;
2085 case ADDRPART_LOCALPART: part = extracted_addr; part[domain-1] = '\0'; break;
2086 case ADDRPART_DOMAIN: part = extracted_addr+domain; break;
2088 case ADDRPART_DETAIL: part = NULL; break;
2092 *end_addr = saveend;
2093 if (part && extracted_addr)
2095 gstring partStr = {.s = part, .ptr = Ustrlen(part), .size = Ustrlen(part)+1};
2096 for (gstring * k = key; k->ptr != - 1; ++k)
2098 *cond = compare(filter, k, &partStr, comparator, matchType);
2099 if (*cond == -1) return -1;
2104 if (saveend == 0) break;
2105 header_value = end_addr + 1;
2107 f.parse_allow_group = FALSE;
2108 f.parse_found_group = FALSE;
2113 else if (parse_identifier(filter, CUS "allof"))
2116 allof-test = "allof" <tests: test-list>
2121 switch (parse_testlist(filter, &n, &num_true, exec))
2124 case 0: filter->errmsg = CUS "missing test list"; return -1;
2125 default: *cond = (n == num_true); return 1;
2128 else if (parse_identifier(filter, CUS "anyof"))
2131 anyof-test = "anyof" <tests: test-list>
2136 switch (parse_testlist(filter, &n, &num_true, exec))
2139 case 0: filter->errmsg = CUS "missing test list"; return -1;
2140 default: *cond = (num_true>0); return 1;
2143 else if (parse_identifier(filter, CUS "exists"))
2146 exists-test = "exists" <header-names: string-list>
2152 if (parse_white(filter) == -1)
2154 if ((m = parse_stringlist(filter, &hdr)) != 1)
2156 if (m == 0) filter->errmsg = CUS "header string list expected";
2162 for (gstring * h = hdr; h->ptr != -1 && *cond; ++h)
2166 header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
2169 filter->errmsg = CUS "header string expansion failed";
2172 if (Ustrcmp(header_def,"false") == 0) *cond = 0;
2177 else if (parse_identifier(filter, CUS "false"))
2180 false-test = "false"
2186 else if (parse_identifier(filter, CUS "header"))
2189 header-test = "header" { [comparator] [match-type] }
2190 <header-names: string-list> <key-list: string-list>
2193 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2194 enum MatchType matchType = MATCH_IS;
2201 if (parse_white(filter) == -1)
2203 if ((m = parse_comparator(filter, &comparator)) != 0)
2205 if (m == -1) return -1;
2208 filter->errmsg = CUS "comparator already specified";
2213 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2215 if (m == -1) return -1;
2218 filter->errmsg = CUS "match type already specified";
2225 if (parse_white(filter) == -1)
2227 if ((m = parse_stringlist(filter, &hdr)) != 1)
2229 if (m == 0) filter->errmsg = CUS "header string list expected";
2232 if (parse_white(filter) == -1)
2234 if ((m = parse_stringlist(filter, &key)) != 1)
2236 if (m == 0) filter->errmsg = CUS "key string list expected";
2240 for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
2244 filter->errmsg = CUS "invalid header field";
2249 gstring header_value;
2252 expand_header(&header_value, h);
2253 header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
2254 if (!header_value.s || !header_def)
2256 filter->errmsg = CUS "header string expansion failed";
2259 for (gstring * k = key; k->ptr != -1; ++k)
2260 if (Ustrcmp(header_def,"true") == 0)
2262 *cond = compare(filter, k, &header_value, comparator, matchType);
2263 if (*cond == -1) return -1;
2270 else if (parse_identifier(filter, CUS "not"))
2272 if (parse_white(filter) == -1) return -1;
2273 switch (parse_test(filter, cond, exec))
2276 case 0: filter->errmsg = CUS "missing test"; return -1;
2277 default: *cond = !*cond; return 1;
2280 else if (parse_identifier(filter, CUS "size"))
2283 relop = ":over" / ":under"
2284 size-test = "size" relop <limit: number>
2287 unsigned long limit;
2290 if (parse_white(filter) == -1) return -1;
2291 if (parse_identifier(filter, CUS ":over")) overNotUnder = 1;
2292 else if (parse_identifier(filter, CUS ":under")) overNotUnder = 0;
2295 filter->errmsg = CUS "missing :over or :under";
2298 if (parse_white(filter) == -1) return -1;
2299 if (parse_number(filter, &limit) == -1) return -1;
2300 *cond = (overNotUnder ? (message_size>limit) : (message_size<limit));
2303 else if (parse_identifier(filter, CUS "true"))
2308 else if (parse_identifier(filter, CUS "envelope"))
2311 envelope-test = "envelope" { [comparator] [address-part] [match-type] }
2312 <envelope-part: string-list> <key-list: string-list>
2314 envelope-part is case insensitive "from" or "to"
2315 #ifdef ENVELOPE_AUTH
2316 envelope-part = / "auth"
2320 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2321 enum AddressPart addressPart = ADDRPART_ALL;
2322 enum MatchType matchType = MATCH_IS;
2325 int co = 0, ap = 0, mt = 0;
2327 if (!filter->require_envelope)
2329 filter->errmsg = CUS "missing previous require \"envelope\";";
2334 if (parse_white(filter) == -1) return -1;
2335 if ((m = parse_comparator(filter, &comparator)) != 0)
2337 if (m == -1) return -1;
2340 filter->errmsg = CUS "comparator already specified";
2345 else if ((m = parse_addresspart(filter, &addressPart)) != 0)
2347 if (m == -1) return -1;
2350 filter->errmsg = CUS "address part already specified";
2355 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2357 if (m == -1) return -1;
2360 filter->errmsg = CUS "match type already specified";
2367 if (parse_white(filter) == -1)
2369 if ((m = parse_stringlist(filter, &env)) != 1)
2371 if (m == 0) filter->errmsg = CUS "envelope string list expected";
2374 if (parse_white(filter) == -1)
2376 if ((m = parse_stringlist(filter, &key)) != 1)
2378 if (m == 0) filter->errmsg = CUS "key string list expected";
2382 for (gstring * e = env; e->ptr != -1 && !*cond; ++e)
2384 const uschar *envelopeExpr = CUS 0;
2385 uschar *envelope = US 0;
2387 if (eq_asciicase(e, &str_from, FALSE))
2389 switch (addressPart)
2391 case ADDRPART_ALL: envelopeExpr = CUS "$sender_address"; break;
2395 case ADDRPART_LOCALPART: envelopeExpr = CUS "${local_part:$sender_address}"; break;
2396 case ADDRPART_DOMAIN: envelopeExpr = CUS "${domain:$sender_address}"; break;
2398 case ADDRPART_DETAIL: envelopeExpr = CUS 0; break;
2402 else if (eq_asciicase(e, &str_to, FALSE))
2404 switch (addressPart)
2406 case ADDRPART_ALL: envelopeExpr = CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
2408 case ADDRPART_USER: envelopeExpr = filter->useraddress; break;
2409 case ADDRPART_DETAIL: envelopeExpr = filter->subaddress; break;
2411 case ADDRPART_LOCALPART: envelopeExpr = CUS "$local_part_prefix$local_part$local_part_suffix"; break;
2412 case ADDRPART_DOMAIN: envelopeExpr = CUS "$domain"; break;
2415 #ifdef ENVELOPE_AUTH
2416 else if (eq_asciicase(e, &str_auth, FALSE))
2418 switch (addressPart)
2420 case ADDRPART_ALL: envelopeExpr = CUS "$authenticated_sender"; break;
2424 case ADDRPART_LOCALPART: envelopeExpr = CUS "${local_part:$authenticated_sender}"; break;
2425 case ADDRPART_DOMAIN: envelopeExpr = CUS "${domain:$authenticated_sender}"; break;
2427 case ADDRPART_DETAIL: envelopeExpr = CUS 0; break;
2434 filter->errmsg = CUS "invalid envelope string";
2437 if (exec && envelopeExpr)
2439 if (!(envelope = expand_string(US envelopeExpr)))
2441 filter->errmsg = CUS "header string expansion failed";
2444 for (gstring * k = key; k->ptr != -1; ++k)
2446 gstring envelopeStr = {.s = envelope, .ptr = Ustrlen(envelope), .size = Ustrlen(envelope)+1};
2448 *cond = compare(filter, k, &envelopeStr, comparator, matchType);
2449 if (*cond == -1) return -1;
2457 else if (parse_identifier(filter, CUS "valid_notify_method"))
2460 valid_notify_method = "valid_notify_method"
2461 <notification-uris: string-list>
2467 if (!filter->require_enotify)
2469 filter->errmsg = CUS "missing previous require \"enotify\";";
2472 if (parse_white(filter) == -1)
2474 if ((m = parse_stringlist(filter, &uris)) != 1)
2476 if (m == 0) filter->errmsg = CUS "URI string list expected";
2482 for (gstring * u = uris; u->ptr != -1 && *cond; ++u)
2484 string_item * recipient = NULL;
2485 gstring header = { .s = NULL, .ptr = -1 };
2486 gstring subject = { .s = NULL, .ptr = -1 };
2487 gstring body = { .s = NULL, .ptr = -1 };
2489 if (parse_mailto_uri(filter, u->s, &recipient, &header, &subject, &body) != 1)
2495 else if (parse_identifier(filter, CUS "notify_method_capability"))
2498 notify_method_capability = "notify_method_capability" [COMPARATOR] [MATCH-TYPE]
2499 <notification-uri: string>
2500 <notification-capability: string>
2501 <key-list: string-list>
2507 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2508 enum MatchType matchType = MATCH_IS;
2509 gstring uri, capa, *keys;
2511 if (!filter->require_enotify)
2513 filter->errmsg = CUS "missing previous require \"enotify\";";
2518 if (parse_white(filter) == -1) return -1;
2519 if ((m = parse_comparator(filter, &comparator)) != 0)
2521 if (m == -1) return -1;
2524 filter->errmsg = CUS "comparator already specified";
2529 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2531 if (m == -1) return -1;
2534 filter->errmsg = CUS "match type already specified";
2541 if ((m = parse_string(filter, &uri)) != 1)
2543 if (m == 0) filter->errmsg = CUS "missing notification URI string";
2546 if (parse_white(filter) == -1)
2548 if ((m = parse_string(filter, &capa)) != 1)
2550 if (m == 0) filter->errmsg = CUS "missing notification capability string";
2553 if (parse_white(filter) == -1)
2555 if ((m = parse_stringlist(filter, &keys)) != 1)
2557 if (m == 0) filter->errmsg = CUS "missing key string list";
2562 string_item * recipient = NULL;
2563 gstring header = { .s = NULL, .ptr = -1 };
2564 gstring subject = { .s = NULL, .ptr = -1 };
2565 gstring body = { .s = NULL, .ptr = -1 };
2568 if (parse_mailto_uri(filter, uri.s, &recipient, &header, &subject, &body) == 1)
2569 if (eq_asciicase(&capa, &str_online, FALSE) == 1)
2570 for (gstring * k = keys; k->ptr != -1; ++k)
2572 *cond = compare(filter, k, &str_maybe, comparator, matchType);
2573 if (*cond == -1) return -1;
2584 /*************************************************
2585 * Parse and interpret an optional block *
2586 *************************************************/
2590 filter points to the Sieve filter including its state
2591 exec Execute parsed statements
2592 generated where to hang newly-generated addresses
2594 Returns: 2 success by stop
2596 0 no block command found
2597 -1 syntax or execution error
2601 parse_block(struct Sieve * filter, int exec, address_item ** generated)
2605 if (parse_white(filter) == -1)
2607 if (*filter->pc == '{')
2610 if ((r = parse_commands(filter, exec, generated)) == -1 || r == 2) return r;
2611 if (*filter->pc == '}')
2616 filter->errmsg = CUS "expecting command or closing brace";
2623 /*************************************************
2624 * Match a semicolon *
2625 *************************************************/
2629 filter points to the Sieve filter including its state
2636 parse_semicolon(struct Sieve *filter)
2638 if (parse_white(filter) == -1)
2640 if (*filter->pc == ';')
2645 filter->errmsg = CUS "missing semicolon";
2650 /*************************************************
2651 * Parse and interpret a Sieve command *
2652 *************************************************/
2656 filter points to the Sieve filter including its state
2657 exec Execute parsed statements
2658 generated where to hang newly-generated addresses
2660 Returns: 2 success by stop
2662 -1 syntax or execution error
2665 parse_commands(struct Sieve *filter, int exec, address_item **generated)
2669 if (parse_white(filter) == -1)
2671 if (parse_identifier(filter, CUS "if"))
2674 if-command = "if" test block *( "elsif" test block ) [ else block ]
2677 int cond, m, unsuccessful;
2680 if (parse_white(filter) == -1)
2682 if ((m = parse_test(filter, &cond, exec)) == -1)
2686 filter->errmsg = CUS "missing test";
2689 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
2690 (debug_selector & D_filter) != 0)
2692 if (exec) debug_printf_indent("if %s\n", cond?"true":"false");
2694 m = parse_block(filter, exec ? cond : 0, generated);
2695 if (m == -1 || m == 2)
2699 filter->errmsg = CUS "missing block";
2702 unsuccessful = !cond;
2703 for (;;) /* elsif test block */
2705 if (parse_white(filter) == -1)
2707 if (parse_identifier(filter, CUS "elsif"))
2709 if (parse_white(filter) == -1)
2711 m = parse_test(filter, &cond, exec && unsuccessful);
2712 if (m == -1 || m == 2)
2716 filter->errmsg = CUS "missing test";
2719 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
2720 (debug_selector & D_filter) != 0)
2722 if (exec) debug_printf_indent("elsif %s\n", cond?"true":"false");
2724 m = parse_block(filter, exec && unsuccessful ? cond : 0, generated);
2725 if (m == -1 || m == 2)
2729 filter->errmsg = CUS "missing block";
2732 if (exec && unsuccessful && cond)
2738 if (parse_white(filter) == -1)
2740 if (parse_identifier(filter, CUS "else"))
2742 m = parse_block(filter, exec && unsuccessful, generated);
2743 if (m == -1 || m == 2)
2747 filter->errmsg = CUS "missing block";
2752 else if (parse_identifier(filter, CUS "stop"))
2755 stop-command = "stop" { stop-options } ";"
2759 if (parse_semicolon(filter) == -1)
2763 filter->pc += Ustrlen(filter->pc);
2767 else if (parse_identifier(filter, CUS "keep"))
2770 keep-command = "keep" { keep-options } ";"
2774 if (parse_semicolon(filter) == -1)
2778 add_addr(generated, US"inbox", 1, 0, 0, 0);
2782 else if (parse_identifier(filter, CUS "discard"))
2785 discard-command = "discard" { discard-options } ";"
2789 if (parse_semicolon(filter) == -1)
2791 if (exec) filter->keep = 0;
2793 else if (parse_identifier(filter, CUS "redirect"))
2796 redirect-command = "redirect" redirect-options "string" ";"
2798 redirect-options = ) ":copy"
2807 if (parse_white(filter) == -1)
2809 if (parse_identifier(filter, CUS ":copy") == 1)
2811 if (!filter->require_copy)
2813 filter->errmsg = CUS "missing previous require \"copy\";";
2820 if (parse_white(filter) == -1)
2822 if ((m = parse_string(filter, &recipient)) != 1)
2825 filter->errmsg = CUS "missing redirect recipient string";
2828 if (strchr(CCS recipient.s, '@') == NULL)
2830 filter->errmsg = CUS "unqualified recipient address";
2835 add_addr(generated, recipient.s, 0, 0, 0, 0);
2836 if (!copy) filter->keep = 0;
2838 if (parse_semicolon(filter) == -1) return -1;
2840 else if (parse_identifier(filter, CUS "fileinto"))
2843 fileinto-command = "fileinto" { fileinto-options } string ";"
2845 fileinto-options = ) [ ":copy" ]
2851 unsigned long maxage, maxmessages, maxstorage;
2854 maxage = maxmessages = maxstorage = 0;
2855 if (!filter->require_fileinto)
2857 filter->errmsg = CUS "missing previous require \"fileinto\";";
2862 if (parse_white(filter) == -1)
2864 if (parse_identifier(filter, CUS ":copy") == 1)
2866 if (!filter->require_copy)
2868 filter->errmsg = CUS "missing previous require \"copy\";";
2875 if (parse_white(filter) == -1)
2877 if ((m = parse_string(filter, &folder)) != 1)
2879 if (m == 0) filter->errmsg = CUS "missing fileinto folder string";
2882 m = 0; s = folder.s;
2883 if (folder.ptr == 0)
2885 if (Ustrcmp(s,"..") == 0 || Ustrncmp(s,"../", 3) == 0)
2889 if (Ustrcmp(s,"/..") == 0 || Ustrncmp(s,"/../", 4) == 0) { m = 1; break; }
2894 filter->errmsg = CUS "invalid folder";
2899 add_addr(generated, folder.s, 1, maxage, maxmessages, maxstorage);
2900 if (!copy) filter->keep = 0;
2902 if (parse_semicolon(filter) == -1)
2906 else if (parse_identifier(filter, CUS "notify"))
2909 notify-command = "notify" { notify-options } <method: string> ";"
2910 notify-options = [":from" string]
2911 [":importance" <"1" / "2" / "3">]
2912 [":options" 1*(string-list / number)]
2917 gstring from = { .s = NULL, .ptr = -1 };
2918 gstring importance = { .s = NULL, .ptr = -1 };
2919 gstring message = { .s = NULL, .ptr = -1 };
2921 struct Notification *already;
2922 string_item * recipient = NULL;
2923 gstring header = { .s = NULL, .ptr = -1 };
2924 gstring subject = { .s = NULL, .ptr = -1 };
2925 gstring body = { .s = NULL, .ptr = -1 };
2926 uschar *envelope_from;
2927 gstring auto_submitted_value;
2928 uschar *auto_submitted_def;
2930 if (!filter->require_enotify)
2932 filter->errmsg = CUS "missing previous require \"enotify\";";
2935 envelope_from = sender_address && sender_address[0]
2936 ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : US "";
2939 filter->errmsg = CUS "expansion failure for envelope from";
2944 if (parse_white(filter) == -1)
2946 if (parse_identifier(filter, CUS ":from") == 1)
2948 if (parse_white(filter) == -1)
2950 if ((m = parse_string(filter, &from)) != 1)
2952 if (m == 0) filter->errmsg = CUS "from string expected";
2956 else if (parse_identifier(filter, CUS ":importance") == 1)
2958 if (parse_white(filter) == -1)
2960 if ((m = parse_string(filter, &importance)) != 1)
2963 filter->errmsg = CUS "importance string expected";
2966 if (importance.ptr != 1 || importance.s[0] < '1' || importance.s[0] > '3')
2968 filter->errmsg = CUS "invalid importance";
2972 else if (parse_identifier(filter, CUS ":options") == 1)
2974 if (parse_white(filter) == -1)
2977 else if (parse_identifier(filter, CUS ":message") == 1)
2979 if (parse_white(filter) == -1)
2981 if ((m = parse_string(filter, &message)) != 1)
2984 filter->errmsg = CUS "message string expected";
2990 if (parse_white(filter) == -1)
2992 if ((m = parse_string(filter, &method)) != 1)
2995 filter->errmsg = CUS "missing method string";
2998 if (parse_semicolon(filter) == -1)
3000 if (parse_mailto_uri(filter, method.s, &recipient, &header, &subject, &body) != 1)
3004 if (message.ptr == -1)
3006 if (message.ptr == -1)
3007 expand_header(&message, &str_subject);
3008 expand_header(&auto_submitted_value, &str_auto_submitted);
3009 auto_submitted_def = expand_string(US"${if def:header_auto-submitted {true}{false}}");
3010 if (!auto_submitted_value.s || !auto_submitted_def)
3012 filter->errmsg = CUS "header string expansion failed";
3015 if (Ustrcmp(auto_submitted_def,"true") != 0 || Ustrcmp(auto_submitted_value.s,"no") == 0)
3017 for (already = filter->notified; already; already = already->next)
3019 if ( already->method.ptr == method.ptr
3020 && (method.ptr == -1 || Ustrcmp(already->method.s, method.s) == 0)
3021 && already->importance.ptr == importance.ptr
3022 && (importance.ptr == -1 || Ustrcmp(already->importance.s, importance.s) == 0)
3023 && already->message.ptr == message.ptr
3024 && (message.ptr == -1 || Ustrcmp(already->message.s, message.s) == 0))
3028 /* New notification, process it */
3030 struct Notification * sent = store_get(sizeof(struct Notification), GET_UNTAINTED);
3031 sent->method = method;
3032 sent->importance = importance;
3033 sent->message = message;
3034 sent->next = filter->notified;
3035 filter->notified = sent;
3036 #ifndef COMPILE_SYNTAX_CHECKER
3037 if (filter_test == FTEST_NONE)
3041 if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
3042 US"sieve-notify")) >= 1)
3044 FILE * f = fdopen(fd, "wb");
3046 fprintf(f,"From: %s\n", from.ptr == -1
3047 ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain")
3049 for (string_item * p = recipient; p; p = p->next)
3050 fprintf(f, "To: %s\n", p->text);
3051 fprintf(f, "Auto-Submitted: auto-notified; %s\n", filter->enotify_mailto_owner);
3052 if (header.ptr > 0) fprintf(f, "%s", header.s);
3053 if (message.ptr == -1)
3055 message.s = US"Notification";
3056 message.ptr = Ustrlen(message.s);
3058 if (message.ptr != -1)
3059 fprintf(f, "Subject: %s\n", parse_quote_2047(message.s,
3060 message.ptr, US"utf-8", TRUE));
3062 if (body.ptr > 0) fprintf(f, "%s\n", body.s);
3065 (void)child_close(pid, 0);
3068 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3069 debug_printf_indent("Notification to `%s': '%s'.\n", method.s, message.ptr != -1 ? message.s : CUS "");
3073 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3074 debug_printf_indent("Repeated notification to `%s' ignored.\n", method.s);
3077 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3078 debug_printf_indent("Ignoring notification, triggering message contains Auto-submitted: field.\n");
3083 else if (parse_identifier(filter, CUS "vacation"))
3086 vacation-command = "vacation" { vacation-options } <reason: string> ";"
3087 vacation-options = [":days" number]
3090 [":addresses" string-list]
3101 string_item *aliases;
3105 if (!filter->require_vacation)
3107 filter->errmsg = CUS "missing previous require \"vacation\";";
3112 if (filter->vacation_ran)
3114 filter->errmsg = CUS "trying to execute vacation more than once";
3117 filter->vacation_ran = TRUE;
3119 days = VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
3120 subject.s = (uschar*)0;
3122 from.s = (uschar*)0;
3124 addresses = (gstring*)0;
3127 handle.s = (uschar*)0;
3131 if (parse_white(filter) == -1)
3133 if (parse_identifier(filter, CUS ":days") == 1)
3135 if (parse_white(filter) == -1)
3137 if (parse_number(filter, &days) == -1)
3139 if (days<VACATION_MIN_DAYS)
3140 days = VACATION_MIN_DAYS;
3141 else if (days>VACATION_MAX_DAYS)
3142 days = VACATION_MAX_DAYS;
3144 else if (parse_identifier(filter, CUS ":subject") == 1)
3146 if (parse_white(filter) == -1)
3148 if ((m = parse_string(filter, &subject)) != 1)
3151 filter->errmsg = CUS "subject string expected";
3155 else if (parse_identifier(filter, CUS ":from") == 1)
3157 if (parse_white(filter) == -1)
3159 if ((m = parse_string(filter, &from)) != 1)
3162 filter->errmsg = CUS "from string expected";
3165 if (check_mail_address(filter, &from) != 1)
3168 else if (parse_identifier(filter, CUS ":addresses") == 1)
3170 if (parse_white(filter) == -1)
3172 if ((m = parse_stringlist(filter, &addresses)) != 1)
3175 filter->errmsg = CUS "addresses string list expected";
3178 for (gstring * a = addresses; a->ptr != -1; ++a)
3180 string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
3182 new->text = store_get(a->ptr+1, a->s);
3183 if (a->ptr) memcpy(new->text, a->s, a->ptr);
3184 new->text[a->ptr] = '\0';
3185 new->next = aliases;
3189 else if (parse_identifier(filter, CUS ":mime") == 1)
3191 else if (parse_identifier(filter, CUS ":handle") == 1)
3193 if (parse_white(filter) == -1)
3195 if ((m = parse_string(filter, &from)) != 1)
3198 filter->errmsg = CUS "handle string expected";
3204 if (parse_white(filter) == -1)
3206 if ((m = parse_string(filter, &reason)) != 1)
3209 filter->errmsg = CUS "missing reason string";
3216 for (s = reason.s, end = reason.s + reason.ptr;
3217 s<end && (*s&0x80) == 0; ) s++;
3220 filter->errmsg = CUS "MIME reason string contains 8bit text";
3224 if (parse_semicolon(filter) == -1) return -1;
3231 uschar hexdigest[33];
3234 if (filter_personal(aliases, TRUE))
3236 if (filter_test == FTEST_NONE)
3238 /* ensure oncelog directory exists; failure will be detected later */
3240 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
3242 /* build oncelog filename */
3246 if (handle.ptr == -1)
3248 gstring * key = NULL;
3249 if (subject.ptr != -1)
3250 key = string_catn(key, subject.s, subject.ptr);
3252 key = string_catn(key, from.s, from.ptr);
3253 key = string_catn(key, reason_is_mime?US"1":US"0", 1);
3254 key = string_catn(key, reason.s, reason.ptr);
3255 md5_end(&base, key->s, key->ptr, digest);
3258 md5_end(&base, handle.s, handle.ptr, digest);
3260 for (int i = 0; i < 16; i++)
3261 sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
3263 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
3264 debug_printf_indent("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
3266 if (filter_test == FTEST_NONE)
3268 once = string_cat (NULL, filter->vacation_directory);
3269 once = string_catn(once, US"/", 1);
3270 once = string_catn(once, hexdigest, 33);
3272 /* process subject */
3274 if (subject.ptr == -1)
3276 uschar * subject_def;
3278 subject_def = expand_string(US"${if def:header_subject {true}{false}}");
3279 if (subject_def && Ustrcmp(subject_def,"true") == 0)
3281 gstring * g = string_catn(NULL, US"Auto: ", 6);
3283 expand_header(&subject, &str_subject);
3284 g = string_catn(g, subject.s, subject.ptr);
3285 subject.ptr = len_string_from_gstring(g, &subject.s);
3289 subject.s = US"Automated reply";
3290 subject.ptr = Ustrlen(subject.s);
3294 /* add address to list of generated addresses */
3296 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
3297 setflag(addr, af_pfr);
3298 addr->prop.ignore_error = TRUE;
3299 addr->next = *generated;
3301 addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED);
3302 memset(addr->reply, 0, sizeof(reply_item)); /* XXX */
3303 addr->reply->to = string_copy(sender_address);
3305 addr->reply->from = expand_string(US"$local_part@$domain");
3307 addr->reply->from = from.s;
3308 /* deconst cast safe as we pass in a non-const item */
3309 addr->reply->subject = US parse_quote_2047(subject.s, subject.ptr, US"utf-8", TRUE);
3310 addr->reply->oncelog = string_from_gstring(once);
3311 addr->reply->once_repeat = days*86400;
3313 /* build body and MIME headers */
3317 uschar *mime_body, *reason_end;
3318 static const uschar nlnl[] = "\r\n\r\n";
3322 mime_body = reason.s, reason_end = reason.s + reason.ptr;
3323 mime_body < (reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body, nlnl, (sizeof(nlnl)-1));
3326 addr->reply->headers = string_copyn(reason.s, mime_body-reason.s);
3328 if (mime_body+(sizeof(nlnl)-1)<reason_end)
3329 mime_body += (sizeof(nlnl)-1);
3330 else mime_body = reason_end-1;
3331 addr->reply->text = string_copyn(mime_body, reason_end-mime_body);
3335 addr->reply->headers = US"MIME-Version: 1.0\n"
3336 "Content-Type: text/plain;\n"
3337 "\tcharset=\"utf-8\"\n"
3338 "Content-Transfer-Encoding: quoted-printable";
3339 addr->reply->text = quoted_printable_encode(&reason)->s;
3343 else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
3344 debug_printf_indent("Sieve: mail was not personal, vacation would ignore it\n");
3354 /*************************************************
3355 * Parse and interpret a sieve filter *
3356 *************************************************/
3360 filter points to the Sieve filter including its state
3361 exec Execute parsed statements
3362 generated where to hang newly-generated addresses
3365 -1 syntax or execution error
3369 parse_start(struct Sieve *filter, int exec, address_item **generated)
3371 filter->pc = filter->filter;
3374 filter->require_envelope = 0;
3375 filter->require_fileinto = 0;
3376 #ifdef ENCODED_CHARACTER
3377 filter->require_encoded_character = FALSE;
3379 #ifdef ENVELOPE_AUTH
3380 filter->require_envelope_auth = 0;
3383 filter->require_enotify = 0;
3384 filter->notified = (struct Notification*)0;
3387 filter->require_subaddress = FALSE;
3390 filter->require_vacation = FALSE;
3391 filter->vacation_ran = 0; /*XXX missing init? */
3393 filter->require_copy = FALSE;
3394 filter->require_iascii_numeric = FALSE;
3396 if (parse_white(filter) == -1) return -1;
3398 if (exec && filter->vacation_directory && filter_test == FTEST_NONE)
3401 struct dirent *oncelog;
3402 struct stat properties;
3405 /* clean up old vacation log databases */
3407 if ( !(oncelogdir = exim_opendir(filter->vacation_directory))
3410 filter->errmsg = CUS "unable to open vacation directory";
3418 while ((oncelog = readdir(oncelogdir)))
3419 if (strlen(oncelog->d_name) == 32)
3421 uschar *s = string_sprintf("%s/%s", filter->vacation_directory, oncelog->d_name);
3422 if (Ustat(s, &properties) == 0 && properties.st_mtime+VACATION_MAX_DAYS*86400 < now)
3425 closedir(oncelogdir);
3429 while (parse_identifier(filter, CUS "require"))
3432 require-command = "require" <capabilities: string-list>
3438 if (parse_white(filter) == -1) return -1;
3439 if ((m = parse_stringlist(filter, &cap)) != 1)
3441 if (m == 0) filter->errmsg = CUS "capability string list expected";
3444 for (gstring * check = cap; check->s; ++check)
3446 if (eq_octet(check, &str_envelope, FALSE)) filter->require_envelope = 1;
3447 else if (eq_octet(check, &str_fileinto, FALSE)) filter->require_fileinto = 1;
3448 #ifdef ENCODED_CHARACTER
3449 else if (eq_octet(check, &str_encoded_character, FALSE)) filter->require_encoded_character = TRUE;
3451 #ifdef ENVELOPE_AUTH
3452 else if (eq_octet(check, &str_envelope_auth, FALSE)) filter->require_envelope_auth = 1;
3455 else if (eq_octet(check, &str_enotify, FALSE))
3457 if (!filter->enotify_mailto_owner)
3459 filter->errmsg = CUS "enotify disabled";
3462 filter->require_enotify = 1;
3466 else if (eq_octet(check, &str_subaddress, FALSE)) filter->require_subaddress = TRUE;
3469 else if (eq_octet(check, &str_vacation, FALSE))
3471 if (filter_test == FTEST_NONE && !filter->vacation_directory)
3473 filter->errmsg = CUS "vacation disabled";
3476 filter->require_vacation = TRUE;
3479 else if (eq_octet(check, &str_copy, FALSE)) filter->require_copy = TRUE;
3480 else if (eq_octet(check, &str_comparator_ioctet, FALSE)) ;
3481 else if (eq_octet(check, &str_comparator_iascii_casemap, FALSE)) ;
3482 else if (eq_octet(check, &str_comparator_enascii_casemap, FALSE)) ;
3483 else if (eq_octet(check, &str_comparator_iascii_numeric, FALSE)) filter->require_iascii_numeric = TRUE;
3486 filter->errmsg = CUS "unknown capability";
3490 if (parse_semicolon(filter) == -1) return -1;
3492 if (parse_commands(filter, exec, generated) == -1) return -1;
3495 filter->errmsg = CUS "syntax error";
3502 /*************************************************
3503 * Interpret a sieve filter file *
3504 *************************************************/
3508 filter points to the entire file, read into store as a single string
3509 options controls whether various special things are allowed, and requests
3510 special actions (not currently used)
3511 vacation_directory where to store vacation "once" files
3512 enotify_mailto_owner owner of mailto notifications
3513 useraddress string expression for :user part of address
3514 subaddress string expression for :subaddress part of address
3515 generated where to hang newly-generated addresses
3516 error where to pass back an error text
3518 Returns: FF_DELIVERED success, a significant action was taken
3519 FF_NOTDELIVERED success, no significant action
3520 FF_DEFER defer requested
3521 FF_FAIL fail requested
3522 FF_FREEZE freeze requested
3523 FF_ERROR there was a problem
3527 sieve_interpret(const uschar * filter, int options,
3528 const uschar * vacation_directory, const uschar * enotify_mailto_owner,
3529 const uschar * useraddress, const uschar * subaddress,
3530 address_item ** generated, uschar ** error)
3536 DEBUG(D_route) debug_printf_indent("Sieve: start of processing\n");
3538 sieve.filter = filter;
3540 if (!vacation_directory)
3541 sieve.vacation_directory = NULL;
3542 else if (!(sieve.vacation_directory = expand_cstring(vacation_directory)))
3544 *error = string_sprintf("failed to expand \"%s\" "
3545 "(sieve_vacation_directory): %s", vacation_directory,
3546 expand_string_message);
3550 if (!enotify_mailto_owner)
3551 sieve.enotify_mailto_owner = NULL;
3552 else if (!(sieve.enotify_mailto_owner = expand_cstring(enotify_mailto_owner)))
3554 *error = string_sprintf("failed to expand \"%s\" "
3555 "(sieve_enotify_mailto_owner): %s", enotify_mailto_owner,
3556 expand_string_message);
3560 sieve.useraddress = useraddress
3561 ? useraddress : CUS "$local_part_prefix$local_part$local_part_suffix";
3562 sieve.subaddress = subaddress;
3564 #ifdef COMPILE_SYNTAX_CHECKER
3565 if (parse_start(&sieve, 0, generated) == 1)
3567 if (parse_start(&sieve, 1, generated) == 1)
3571 add_addr(generated, US"inbox", 1, 0, 0, 0);
3572 msg = US"Implicit keep";
3577 msg = US"No implicit keep";
3582 msg = string_sprintf("Sieve error: %s in line %d", sieve.errmsg, sieve.line);
3583 #ifdef COMPILE_SYNTAX_CHECKER
3587 add_addr(generated, US"inbox", 1, 0, 0, 0);
3592 #ifndef COMPILE_SYNTAX_CHECKER
3593 if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
3594 else debug_printf_indent("%s\n", msg);
3598 DEBUG(D_route) debug_printf_indent("Sieve: end of processing\n");