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;
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 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)
1890 else if (parse_identifier(filter, CUS ":contains") == 1)
1892 *m = MATCH_CONTAINS;
1895 else if (parse_identifier(filter, CUS ":matches") == 1)
1904 /*************************************************
1905 * Parse and interpret an optional test list *
1906 *************************************************/
1910 test-list = "(" test *("," test) ")"
1913 filter points to the Sieve filter including its state
1914 n total number of tests
1915 num_true number of passed tests
1916 exec Execute parsed statements
1919 0 no test list found
1920 -1 syntax or execution error
1924 parse_testlist(struct Sieve *filter, int *n, int *num_true, int exec)
1926 if (parse_white(filter) == -1) return -1;
1927 if (*filter->pc == '(')
1936 switch (parse_test(filter, &cond, exec))
1939 case 0: filter->errmsg = CUS "missing test"; return -1;
1940 default: ++*n; if (cond) ++*num_true; break;
1942 if (parse_white(filter) == -1) return -1;
1943 if (*filter->pc == ',') ++filter->pc;
1946 if (*filter->pc == ')')
1953 filter->errmsg = CUS "missing closing paren";
1961 /*************************************************
1962 * Parse and interpret an optional test *
1963 *************************************************/
1967 filter points to the Sieve filter including its state
1968 cond returned condition status
1969 exec Execute parsed statements
1973 -1 syntax or execution error
1977 parse_test(struct Sieve *filter, int *cond, int exec)
1979 if (parse_white(filter) == -1) return -1;
1980 if (parse_identifier(filter, CUS "address"))
1983 address-test = "address" { [address-part] [comparator] [match-type] }
1984 <header-list: string-list> <key-list: string-list>
1986 header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
1989 enum AddressPart addressPart = ADDRPART_ALL;
1990 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
1991 enum MatchType matchType = MATCH_IS;
1994 int ap = 0, co = 0, mt = 0;
1998 if (parse_white(filter) == -1) return -1;
1999 if ((m = parse_addresspart(filter, &addressPart)) != 0)
2001 if (m == -1) return -1;
2004 filter->errmsg = CUS "address part already specified";
2009 else if ((m = parse_comparator(filter, &comparator)) != 0)
2011 if (m == -1) return -1;
2014 filter->errmsg = CUS "comparator already specified";
2019 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2021 if (m == -1) return -1;
2024 filter->errmsg = CUS "match type already specified";
2031 if (parse_white(filter) == -1)
2033 if ((m = parse_stringlist(filter, &hdr)) != 1)
2035 if (m == 0) filter->errmsg = CUS "header string list expected";
2038 if (parse_white(filter) == -1)
2040 if ((m = parse_stringlist(filter, &key)) != 1)
2042 if (m == 0) filter->errmsg = CUS "key string list expected";
2046 for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
2048 uschar * header_value = NULL, * extracted_addr, * end_addr;
2050 if ( !eq_asciicase(h, &str_from, FALSE)
2051 && !eq_asciicase(h, &str_to, FALSE)
2052 && !eq_asciicase(h, &str_cc, FALSE)
2053 && !eq_asciicase(h, &str_bcc, FALSE)
2054 && !eq_asciicase(h, &str_sender, FALSE)
2055 && !eq_asciicase(h, &str_resent_from, FALSE)
2056 && !eq_asciicase(h, &str_resent_to, FALSE)
2059 filter->errmsg = CUS "invalid header field";
2064 /* We are only interested in addresses below, so no MIME decoding */
2065 if (!(header_value = expand_string(string_sprintf("$rheader_%s", quote(h)))))
2067 filter->errmsg = CUS "header string expansion failed";
2070 f.parse_allow_group = TRUE;
2071 while (*header_value && !*cond)
2074 int start, end, domain;
2076 uschar *part = NULL;
2078 end_addr = parse_find_address_end(header_value, FALSE);
2079 saveend = *end_addr;
2081 extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
2083 if (extracted_addr) switch (addressPart)
2085 case ADDRPART_ALL: part = extracted_addr; break;
2089 case ADDRPART_LOCALPART: part = extracted_addr; part[domain-1] = '\0'; break;
2090 case ADDRPART_DOMAIN: part = extracted_addr+domain; break;
2092 case ADDRPART_DETAIL: part = NULL; break;
2096 *end_addr = saveend;
2097 if (part && extracted_addr)
2099 gstring partStr = {.s = part, .ptr = Ustrlen(part), .size = Ustrlen(part)+1};
2100 for (gstring * k = key; k->ptr != - 1; ++k)
2102 *cond = compare(filter, k, &partStr, comparator, matchType);
2103 if (*cond == -1) return -1;
2108 if (saveend == 0) break;
2109 header_value = end_addr + 1;
2111 f.parse_allow_group = FALSE;
2112 f.parse_found_group = FALSE;
2117 else if (parse_identifier(filter, CUS "allof"))
2120 allof-test = "allof" <tests: test-list>
2125 switch (parse_testlist(filter, &n, &num_true, exec))
2128 case 0: filter->errmsg = CUS "missing test list"; return -1;
2129 default: *cond = (n == num_true); return 1;
2132 else if (parse_identifier(filter, CUS "anyof"))
2135 anyof-test = "anyof" <tests: test-list>
2140 switch (parse_testlist(filter, &n, &num_true, exec))
2143 case 0: filter->errmsg = CUS "missing test list"; return -1;
2144 default: *cond = (num_true>0); return 1;
2147 else if (parse_identifier(filter, CUS "exists"))
2150 exists-test = "exists" <header-names: string-list>
2156 if (parse_white(filter) == -1)
2158 if ((m = parse_stringlist(filter, &hdr)) != 1)
2160 if (m == 0) filter->errmsg = CUS "header string list expected";
2166 for (gstring * h = hdr; h->ptr != -1 && *cond; ++h)
2170 header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
2173 filter->errmsg = CUS "header string expansion failed";
2176 if (Ustrcmp(header_def,"false") == 0) *cond = 0;
2181 else if (parse_identifier(filter, CUS "false"))
2184 false-test = "false"
2190 else if (parse_identifier(filter, CUS "header"))
2193 header-test = "header" { [comparator] [match-type] }
2194 <header-names: string-list> <key-list: string-list>
2197 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2198 enum MatchType matchType = MATCH_IS;
2205 if (parse_white(filter) == -1)
2207 if ((m = parse_comparator(filter, &comparator)) != 0)
2209 if (m == -1) return -1;
2212 filter->errmsg = CUS "comparator already specified";
2217 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2219 if (m == -1) return -1;
2222 filter->errmsg = CUS "match type already specified";
2229 if (parse_white(filter) == -1)
2231 if ((m = parse_stringlist(filter, &hdr)) != 1)
2233 if (m == 0) filter->errmsg = CUS "header string list expected";
2236 if (parse_white(filter) == -1)
2238 if ((m = parse_stringlist(filter, &key)) != 1)
2240 if (m == 0) filter->errmsg = CUS "key string list expected";
2244 for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
2248 filter->errmsg = CUS "invalid header field";
2253 gstring header_value;
2256 expand_header(&header_value, h);
2257 header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
2258 if (!header_value.s || !header_def)
2260 filter->errmsg = CUS "header string expansion failed";
2263 for (gstring * k = key; k->ptr != -1; ++k)
2264 if (Ustrcmp(header_def,"true") == 0)
2266 *cond = compare(filter, k, &header_value, comparator, matchType);
2267 if (*cond == -1) return -1;
2274 else if (parse_identifier(filter, CUS "not"))
2276 if (parse_white(filter) == -1) return -1;
2277 switch (parse_test(filter, cond, exec))
2280 case 0: filter->errmsg = CUS "missing test"; return -1;
2281 default: *cond = !*cond; return 1;
2284 else if (parse_identifier(filter, CUS "size"))
2287 relop = ":over" / ":under"
2288 size-test = "size" relop <limit: number>
2291 unsigned long limit;
2294 if (parse_white(filter) == -1) return -1;
2295 if (parse_identifier(filter, CUS ":over")) overNotUnder = 1;
2296 else if (parse_identifier(filter, CUS ":under")) overNotUnder = 0;
2299 filter->errmsg = CUS "missing :over or :under";
2302 if (parse_white(filter) == -1) return -1;
2303 if (parse_number(filter, &limit) == -1) return -1;
2304 *cond = (overNotUnder ? (message_size>limit) : (message_size<limit));
2307 else if (parse_identifier(filter, CUS "true"))
2312 else if (parse_identifier(filter, CUS "envelope"))
2315 envelope-test = "envelope" { [comparator] [address-part] [match-type] }
2316 <envelope-part: string-list> <key-list: string-list>
2318 envelope-part is case insensitive "from" or "to"
2319 #ifdef ENVELOPE_AUTH
2320 envelope-part = / "auth"
2324 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2325 enum AddressPart addressPart = ADDRPART_ALL;
2326 enum MatchType matchType = MATCH_IS;
2329 int co = 0, ap = 0, mt = 0;
2331 if (!filter->require_envelope)
2333 filter->errmsg = CUS "missing previous require \"envelope\";";
2338 if (parse_white(filter) == -1) return -1;
2339 if ((m = parse_comparator(filter, &comparator)) != 0)
2341 if (m == -1) return -1;
2344 filter->errmsg = CUS "comparator already specified";
2349 else if ((m = parse_addresspart(filter, &addressPart)) != 0)
2351 if (m == -1) return -1;
2354 filter->errmsg = CUS "address part already specified";
2359 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2361 if (m == -1) return -1;
2364 filter->errmsg = CUS "match type already specified";
2371 if (parse_white(filter) == -1)
2373 if ((m = parse_stringlist(filter, &env)) != 1)
2375 if (m == 0) filter->errmsg = CUS "envelope string list expected";
2378 if (parse_white(filter) == -1)
2380 if ((m = parse_stringlist(filter, &key)) != 1)
2382 if (m == 0) filter->errmsg = CUS "key string list expected";
2386 for (gstring * e = env; e->ptr != -1 && !*cond; ++e)
2388 const uschar *envelopeExpr = CUS 0;
2389 uschar *envelope = US 0;
2391 if (eq_asciicase(e, &str_from, FALSE))
2393 switch (addressPart)
2395 case ADDRPART_ALL: envelopeExpr = CUS "$sender_address"; break;
2399 case ADDRPART_LOCALPART: envelopeExpr = CUS "${local_part:$sender_address}"; break;
2400 case ADDRPART_DOMAIN: envelopeExpr = CUS "${domain:$sender_address}"; break;
2402 case ADDRPART_DETAIL: envelopeExpr = CUS 0; break;
2406 else if (eq_asciicase(e, &str_to, FALSE))
2408 switch (addressPart)
2410 case ADDRPART_ALL: envelopeExpr = CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
2412 case ADDRPART_USER: envelopeExpr = filter->useraddress; break;
2413 case ADDRPART_DETAIL: envelopeExpr = filter->subaddress; break;
2415 case ADDRPART_LOCALPART: envelopeExpr = CUS "$local_part_prefix$local_part$local_part_suffix"; break;
2416 case ADDRPART_DOMAIN: envelopeExpr = CUS "$domain"; break;
2419 #ifdef ENVELOPE_AUTH
2420 else if (eq_asciicase(e, &str_auth, FALSE))
2422 switch (addressPart)
2424 case ADDRPART_ALL: envelopeExpr = CUS "$authenticated_sender"; break;
2428 case ADDRPART_LOCALPART: envelopeExpr = CUS "${local_part:$authenticated_sender}"; break;
2429 case ADDRPART_DOMAIN: envelopeExpr = CUS "${domain:$authenticated_sender}"; break;
2431 case ADDRPART_DETAIL: envelopeExpr = CUS 0; break;
2438 filter->errmsg = CUS "invalid envelope string";
2441 if (exec && envelopeExpr)
2443 if (!(envelope = expand_string(US envelopeExpr)))
2445 filter->errmsg = CUS "header string expansion failed";
2448 for (gstring * k = key; k->ptr != -1; ++k)
2450 gstring envelopeStr = {.s = envelope, .ptr = Ustrlen(envelope), .size = Ustrlen(envelope)+1};
2452 *cond = compare(filter, k, &envelopeStr, comparator, matchType);
2453 if (*cond == -1) return -1;
2461 else if (parse_identifier(filter, CUS "valid_notify_method"))
2464 valid_notify_method = "valid_notify_method"
2465 <notification-uris: string-list>
2471 if (!filter->require_enotify)
2473 filter->errmsg = CUS "missing previous require \"enotify\";";
2476 if (parse_white(filter) == -1)
2478 if ((m = parse_stringlist(filter, &uris)) != 1)
2480 if (m == 0) filter->errmsg = CUS "URI string list expected";
2486 for (gstring * u = uris; u->ptr != -1 && *cond; ++u)
2488 string_item * recipient = NULL;
2489 gstring header = { .s = NULL, .ptr = -1 };
2490 gstring subject = { .s = NULL, .ptr = -1 };
2491 gstring body = { .s = NULL, .ptr = -1 };
2493 if (parse_mailto_uri(filter, u->s, &recipient, &header, &subject, &body) != 1)
2499 else if (parse_identifier(filter, CUS "notify_method_capability"))
2502 notify_method_capability = "notify_method_capability" [COMPARATOR] [MATCH-TYPE]
2503 <notification-uri: string>
2504 <notification-capability: string>
2505 <key-list: string-list>
2511 enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
2512 enum MatchType matchType = MATCH_IS;
2513 gstring uri, capa, *keys;
2515 if (!filter->require_enotify)
2517 filter->errmsg = CUS "missing previous require \"enotify\";";
2522 if (parse_white(filter) == -1) return -1;
2523 if ((m = parse_comparator(filter, &comparator)) != 0)
2525 if (m == -1) return -1;
2528 filter->errmsg = CUS "comparator already specified";
2533 else if ((m = parse_matchtype(filter, &matchType)) != 0)
2535 if (m == -1) return -1;
2538 filter->errmsg = CUS "match type already specified";
2545 if ((m = parse_string(filter, &uri)) != 1)
2547 if (m == 0) filter->errmsg = CUS "missing notification URI string";
2550 if (parse_white(filter) == -1)
2552 if ((m = parse_string(filter, &capa)) != 1)
2554 if (m == 0) filter->errmsg = CUS "missing notification capability string";
2557 if (parse_white(filter) == -1)
2559 if ((m = parse_stringlist(filter, &keys)) != 1)
2561 if (m == 0) filter->errmsg = CUS "missing key string list";
2566 string_item * recipient = NULL;
2567 gstring header = { .s = NULL, .ptr = -1 };
2568 gstring subject = { .s = NULL, .ptr = -1 };
2569 gstring body = { .s = NULL, .ptr = -1 };
2572 if (parse_mailto_uri(filter, uri.s, &recipient, &header, &subject, &body) == 1)
2573 if (eq_asciicase(&capa, &str_online, FALSE) == 1)
2574 for (gstring * k = keys; k->ptr != -1; ++k)
2576 *cond = compare(filter, k, &str_maybe, comparator, matchType);
2577 if (*cond == -1) return -1;
2588 /*************************************************
2589 * Parse and interpret an optional block *
2590 *************************************************/
2594 filter points to the Sieve filter including its state
2595 exec Execute parsed statements
2596 generated where to hang newly-generated addresses
2598 Returns: 2 success by stop
2600 0 no block command found
2601 -1 syntax or execution error
2605 parse_block(struct Sieve * filter, int exec, address_item ** generated)
2609 if (parse_white(filter) == -1)
2611 if (*filter->pc == '{')
2614 if ((r = parse_commands(filter, exec, generated)) == -1 || r == 2) return r;
2615 if (*filter->pc == '}')
2620 filter->errmsg = CUS "expecting command or closing brace";
2627 /*************************************************
2628 * Match a semicolon *
2629 *************************************************/
2633 filter points to the Sieve filter including its state
2640 parse_semicolon(struct Sieve *filter)
2642 if (parse_white(filter) == -1)
2644 if (*filter->pc == ';')
2649 filter->errmsg = CUS "missing semicolon";
2654 /*************************************************
2655 * Parse and interpret a Sieve command *
2656 *************************************************/
2660 filter points to the Sieve filter including its state
2661 exec Execute parsed statements
2662 generated where to hang newly-generated addresses
2664 Returns: 2 success by stop
2666 -1 syntax or execution error
2669 parse_commands(struct Sieve *filter, int exec, address_item **generated)
2673 if (parse_white(filter) == -1)
2675 if (parse_identifier(filter, CUS "if"))
2678 if-command = "if" test block *( "elsif" test block ) [ else block ]
2681 int cond, m, unsuccessful;
2684 if (parse_white(filter) == -1)
2686 if ((m = parse_test(filter, &cond, exec)) == -1)
2690 filter->errmsg = CUS "missing test";
2693 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
2694 (debug_selector & D_filter) != 0)
2696 if (exec) debug_printf_indent("if %s\n", cond?"true":"false");
2698 m = parse_block(filter, exec ? cond : 0, generated);
2699 if (m == -1 || m == 2)
2703 filter->errmsg = CUS "missing block";
2706 unsuccessful = !cond;
2707 for (;;) /* elsif test block */
2709 if (parse_white(filter) == -1)
2711 if (parse_identifier(filter, CUS "elsif"))
2713 if (parse_white(filter) == -1)
2715 m = parse_test(filter, &cond, exec && unsuccessful);
2716 if (m == -1 || m == 2)
2720 filter->errmsg = CUS "missing test";
2723 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
2724 (debug_selector & D_filter) != 0)
2726 if (exec) debug_printf_indent("elsif %s\n", cond?"true":"false");
2728 m = parse_block(filter, exec && unsuccessful ? cond : 0, generated);
2729 if (m == -1 || m == 2)
2733 filter->errmsg = CUS "missing block";
2736 if (exec && unsuccessful && cond)
2742 if (parse_white(filter) == -1)
2744 if (parse_identifier(filter, CUS "else"))
2746 m = parse_block(filter, exec && unsuccessful, generated);
2747 if (m == -1 || m == 2)
2751 filter->errmsg = CUS "missing block";
2756 else if (parse_identifier(filter, CUS "stop"))
2759 stop-command = "stop" { stop-options } ";"
2763 if (parse_semicolon(filter) == -1)
2767 filter->pc += Ustrlen(filter->pc);
2771 else if (parse_identifier(filter, CUS "keep"))
2774 keep-command = "keep" { keep-options } ";"
2778 if (parse_semicolon(filter) == -1)
2782 add_addr(generated, filter->inbox, 1, 0, 0, 0);
2786 else if (parse_identifier(filter, CUS "discard"))
2789 discard-command = "discard" { discard-options } ";"
2793 if (parse_semicolon(filter) == -1)
2795 if (exec) filter->keep = 0;
2797 else if (parse_identifier(filter, CUS "redirect"))
2800 redirect-command = "redirect" redirect-options "string" ";"
2802 redirect-options = ) ":copy"
2811 if (parse_white(filter) == -1)
2813 if (parse_identifier(filter, CUS ":copy") == 1)
2815 if (!filter->require_copy)
2817 filter->errmsg = CUS "missing previous require \"copy\";";
2824 if (parse_white(filter) == -1)
2826 if ((m = parse_string(filter, &recipient)) != 1)
2829 filter->errmsg = CUS "missing redirect recipient string";
2832 if (strchr(CCS recipient.s, '@') == NULL)
2834 filter->errmsg = CUS "unqualified recipient address";
2839 add_addr(generated, recipient.s, 0, 0, 0, 0);
2840 if (!copy) filter->keep = 0;
2842 if (parse_semicolon(filter) == -1) return -1;
2844 else if (parse_identifier(filter, CUS "fileinto"))
2847 fileinto-command = "fileinto" { fileinto-options } string ";"
2849 fileinto-options = ) [ ":copy" ]
2855 unsigned long maxage, maxmessages, maxstorage;
2858 maxage = maxmessages = maxstorage = 0;
2859 if (!filter->require_fileinto)
2861 filter->errmsg = CUS "missing previous require \"fileinto\";";
2866 if (parse_white(filter) == -1)
2868 if (parse_identifier(filter, CUS ":copy") == 1)
2870 if (!filter->require_copy)
2872 filter->errmsg = CUS "missing previous require \"copy\";";
2879 if (parse_white(filter) == -1)
2881 if ((m = parse_string(filter, &folder)) != 1)
2883 if (m == 0) filter->errmsg = CUS "missing fileinto folder string";
2886 m = 0; s = folder.s;
2887 if (folder.ptr == 0)
2889 if (Ustrcmp(s,"..") == 0 || Ustrncmp(s,"../", 3) == 0)
2893 if (Ustrcmp(s,"/..") == 0 || Ustrncmp(s,"/../", 4) == 0) { m = 1; break; }
2898 filter->errmsg = CUS "invalid folder";
2903 add_addr(generated, folder.s, 1, maxage, maxmessages, maxstorage);
2904 if (!copy) filter->keep = 0;
2906 if (parse_semicolon(filter) == -1)
2910 else if (parse_identifier(filter, CUS "notify"))
2913 notify-command = "notify" { notify-options } <method: string> ";"
2914 notify-options = [":from" string]
2915 [":importance" <"1" / "2" / "3">]
2916 [":options" 1*(string-list / number)]
2921 gstring from = { .s = NULL, .ptr = -1 };
2922 gstring importance = { .s = NULL, .ptr = -1 };
2923 gstring message = { .s = NULL, .ptr = -1 };
2925 struct Notification *already;
2926 string_item * recipient = NULL;
2927 gstring header = { .s = NULL, .ptr = -1 };
2928 gstring subject = { .s = NULL, .ptr = -1 };
2929 gstring body = { .s = NULL, .ptr = -1 };
2930 uschar *envelope_from;
2931 gstring auto_submitted_value;
2932 uschar *auto_submitted_def;
2934 if (!filter->require_enotify)
2936 filter->errmsg = CUS "missing previous require \"enotify\";";
2939 envelope_from = sender_address && sender_address[0]
2940 ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : US "";
2943 filter->errmsg = CUS "expansion failure for envelope from";
2948 if (parse_white(filter) == -1)
2950 if (parse_identifier(filter, CUS ":from") == 1)
2952 if (parse_white(filter) == -1)
2954 if ((m = parse_string(filter, &from)) != 1)
2956 if (m == 0) filter->errmsg = CUS "from string expected";
2960 else if (parse_identifier(filter, CUS ":importance") == 1)
2962 if (parse_white(filter) == -1)
2964 if ((m = parse_string(filter, &importance)) != 1)
2967 filter->errmsg = CUS "importance string expected";
2970 if (importance.ptr != 1 || importance.s[0] < '1' || importance.s[0] > '3')
2972 filter->errmsg = CUS "invalid importance";
2976 else if (parse_identifier(filter, CUS ":options") == 1)
2978 if (parse_white(filter) == -1)
2981 else if (parse_identifier(filter, CUS ":message") == 1)
2983 if (parse_white(filter) == -1)
2985 if ((m = parse_string(filter, &message)) != 1)
2988 filter->errmsg = CUS "message string expected";
2994 if (parse_white(filter) == -1)
2996 if ((m = parse_string(filter, &method)) != 1)
2999 filter->errmsg = CUS "missing method string";
3002 if (parse_semicolon(filter) == -1)
3004 if (parse_mailto_uri(filter, method.s, &recipient, &header, &subject, &body) != 1)
3008 if (message.ptr == -1)
3010 if (message.ptr == -1)
3011 expand_header(&message, &str_subject);
3012 expand_header(&auto_submitted_value, &str_auto_submitted);
3013 auto_submitted_def = expand_string(US"${if def:header_auto-submitted {true}{false}}");
3014 if (!auto_submitted_value.s || !auto_submitted_def)
3016 filter->errmsg = CUS "header string expansion failed";
3019 if (Ustrcmp(auto_submitted_def,"true") != 0 || Ustrcmp(auto_submitted_value.s,"no") == 0)
3021 for (already = filter->notified; already; already = already->next)
3023 if ( already->method.ptr == method.ptr
3024 && (method.ptr == -1 || Ustrcmp(already->method.s, method.s) == 0)
3025 && already->importance.ptr == importance.ptr
3026 && (importance.ptr == -1 || Ustrcmp(already->importance.s, importance.s) == 0)
3027 && already->message.ptr == message.ptr
3028 && (message.ptr == -1 || Ustrcmp(already->message.s, message.s) == 0))
3032 /* New notification, process it */
3034 struct Notification * sent = store_get(sizeof(struct Notification), GET_UNTAINTED);
3035 sent->method = method;
3036 sent->importance = importance;
3037 sent->message = message;
3038 sent->next = filter->notified;
3039 filter->notified = sent;
3040 #ifndef COMPILE_SYNTAX_CHECKER
3041 if (filter_test == FTEST_NONE)
3045 if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
3046 US"sieve-notify")) >= 1)
3048 FILE * f = fdopen(fd, "wb");
3050 fprintf(f,"From: %s\n", from.ptr == -1
3051 ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain")
3053 for (string_item * p = recipient; p; p = p->next)
3054 fprintf(f, "To: %s\n", p->text);
3055 fprintf(f, "Auto-Submitted: auto-notified; %s\n", filter->enotify_mailto_owner);
3056 if (header.ptr > 0) fprintf(f, "%s", header.s);
3057 if (message.ptr == -1)
3059 message.s = US"Notification";
3060 message.ptr = Ustrlen(message.s);
3062 if (message.ptr != -1)
3063 fprintf(f, "Subject: %s\n", parse_quote_2047(message.s,
3064 message.ptr, US"utf-8", TRUE));
3066 if (body.ptr > 0) fprintf(f, "%s\n", body.s);
3069 (void)child_close(pid, 0);
3072 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3073 debug_printf_indent("Notification to `%s': '%s'.\n", method.s, message.ptr != -1 ? message.s : CUS "");
3077 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3078 debug_printf_indent("Repeated notification to `%s' ignored.\n", method.s);
3081 if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
3082 debug_printf_indent("Ignoring notification, triggering message contains Auto-submitted: field.\n");
3087 else if (parse_identifier(filter, CUS "vacation"))
3090 vacation-command = "vacation" { vacation-options } <reason: string> ";"
3091 vacation-options = [":days" number]
3094 [":addresses" string-list]
3105 string_item *aliases;
3109 if (!filter->require_vacation)
3111 filter->errmsg = CUS "missing previous require \"vacation\";";
3116 if (filter->vacation_ran)
3118 filter->errmsg = CUS "trying to execute vacation more than once";
3121 filter->vacation_ran = TRUE;
3123 days = VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
3124 subject.s = (uschar*)0;
3126 from.s = (uschar*)0;
3128 addresses = (gstring*)0;
3131 handle.s = (uschar*)0;
3135 if (parse_white(filter) == -1)
3137 if (parse_identifier(filter, CUS ":days") == 1)
3139 if (parse_white(filter) == -1)
3141 if (parse_number(filter, &days) == -1)
3143 if (days<VACATION_MIN_DAYS)
3144 days = VACATION_MIN_DAYS;
3145 else if (days>VACATION_MAX_DAYS)
3146 days = VACATION_MAX_DAYS;
3148 else if (parse_identifier(filter, CUS ":subject") == 1)
3150 if (parse_white(filter) == -1)
3152 if ((m = parse_string(filter, &subject)) != 1)
3155 filter->errmsg = CUS "subject string expected";
3159 else if (parse_identifier(filter, CUS ":from") == 1)
3161 if (parse_white(filter) == -1)
3163 if ((m = parse_string(filter, &from)) != 1)
3166 filter->errmsg = CUS "from string expected";
3169 if (check_mail_address(filter, &from) != 1)
3172 else if (parse_identifier(filter, CUS ":addresses") == 1)
3174 if (parse_white(filter) == -1)
3176 if ((m = parse_stringlist(filter, &addresses)) != 1)
3179 filter->errmsg = CUS "addresses string list expected";
3182 for (gstring * a = addresses; a->ptr != -1; ++a)
3184 string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
3186 new->text = store_get(a->ptr+1, a->s);
3187 if (a->ptr) memcpy(new->text, a->s, a->ptr);
3188 new->text[a->ptr] = '\0';
3189 new->next = aliases;
3193 else if (parse_identifier(filter, CUS ":mime") == 1)
3195 else if (parse_identifier(filter, CUS ":handle") == 1)
3197 if (parse_white(filter) == -1)
3199 if ((m = parse_string(filter, &from)) != 1)
3202 filter->errmsg = CUS "handle string expected";
3208 if (parse_white(filter) == -1)
3210 if ((m = parse_string(filter, &reason)) != 1)
3213 filter->errmsg = CUS "missing reason string";
3220 for (s = reason.s, end = reason.s + reason.ptr;
3221 s<end && (*s&0x80) == 0; ) s++;
3224 filter->errmsg = CUS "MIME reason string contains 8bit text";
3228 if (parse_semicolon(filter) == -1) return -1;
3235 uschar hexdigest[33];
3238 if (filter_personal(aliases, TRUE))
3240 if (filter_test == FTEST_NONE)
3242 /* ensure oncelog directory exists; failure will be detected later */
3244 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
3246 /* build oncelog filename */
3250 if (handle.ptr == -1)
3252 gstring * key = NULL;
3253 if (subject.ptr != -1)
3254 key = string_catn(key, subject.s, subject.ptr);
3256 key = string_catn(key, from.s, from.ptr);
3257 key = string_catn(key, reason_is_mime?US"1":US"0", 1);
3258 key = string_catn(key, reason.s, reason.ptr);
3259 md5_end(&base, key->s, key->ptr, digest);
3262 md5_end(&base, handle.s, handle.ptr, digest);
3264 for (int i = 0; i < 16; i++)
3265 sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
3267 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
3268 debug_printf_indent("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
3270 if (filter_test == FTEST_NONE)
3272 once = string_cat (NULL, filter->vacation_directory);
3273 once = string_catn(once, US"/", 1);
3274 once = string_catn(once, hexdigest, 33);
3276 /* process subject */
3278 if (subject.ptr == -1)
3280 uschar * subject_def;
3282 subject_def = expand_string(US"${if def:header_subject {true}{false}}");
3283 if (subject_def && Ustrcmp(subject_def,"true") == 0)
3285 gstring * g = string_catn(NULL, US"Auto: ", 6);
3287 expand_header(&subject, &str_subject);
3288 g = string_catn(g, subject.s, subject.ptr);
3289 subject.ptr = len_string_from_gstring(g, &subject.s);
3293 subject.s = US"Automated reply";
3294 subject.ptr = Ustrlen(subject.s);
3298 /* add address to list of generated addresses */
3300 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
3301 setflag(addr, af_pfr);
3302 addr->prop.ignore_error = TRUE;
3303 addr->next = *generated;
3305 addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED);
3306 memset(addr->reply, 0, sizeof(reply_item)); /* XXX */
3307 addr->reply->to = string_copy(sender_address);
3309 addr->reply->from = expand_string(US"$local_part@$domain");
3311 addr->reply->from = from.s;
3312 /* deconst cast safe as we pass in a non-const item */
3313 addr->reply->subject = US parse_quote_2047(subject.s, subject.ptr, US"utf-8", TRUE);
3314 addr->reply->oncelog = string_from_gstring(once);
3315 addr->reply->once_repeat = days*86400;
3317 /* build body and MIME headers */
3321 uschar *mime_body, *reason_end;
3322 static const uschar nlnl[] = "\r\n\r\n";
3326 mime_body = reason.s, reason_end = reason.s + reason.ptr;
3327 mime_body < (reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body, nlnl, (sizeof(nlnl)-1));
3330 addr->reply->headers = string_copyn(reason.s, mime_body-reason.s);
3332 if (mime_body+(sizeof(nlnl)-1)<reason_end)
3333 mime_body += (sizeof(nlnl)-1);
3334 else mime_body = reason_end-1;
3335 addr->reply->text = string_copyn(mime_body, reason_end-mime_body);
3339 addr->reply->headers = US"MIME-Version: 1.0\n"
3340 "Content-Type: text/plain;\n"
3341 "\tcharset=\"utf-8\"\n"
3342 "Content-Transfer-Encoding: quoted-printable";
3343 addr->reply->text = quoted_printable_encode(&reason)->s;
3347 else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
3348 debug_printf_indent("Sieve: mail was not personal, vacation would ignore it\n");
3358 /*************************************************
3359 * Parse and interpret a sieve filter *
3360 *************************************************/
3364 filter points to the Sieve filter including its state
3365 exec Execute parsed statements
3366 generated where to hang newly-generated addresses
3369 -1 syntax or execution error
3373 parse_start(struct Sieve *filter, int exec, address_item **generated)
3375 filter->pc = filter->filter;
3378 filter->require_envelope = 0;
3379 filter->require_fileinto = 0;
3380 #ifdef ENCODED_CHARACTER
3381 filter->require_encoded_character = FALSE;
3383 #ifdef ENVELOPE_AUTH
3384 filter->require_envelope_auth = 0;
3387 filter->require_enotify = 0;
3388 filter->notified = (struct Notification*)0;
3391 filter->require_subaddress = FALSE;
3394 filter->require_vacation = FALSE;
3395 filter->vacation_ran = 0; /*XXX missing init? */
3397 filter->require_copy = FALSE;
3398 filter->require_iascii_numeric = FALSE;
3400 if (parse_white(filter) == -1) return -1;
3402 if (exec && filter->vacation_directory && filter_test == FTEST_NONE)
3405 struct dirent *oncelog;
3406 struct stat properties;
3409 /* clean up old vacation log databases */
3411 if ( !(oncelogdir = exim_opendir(filter->vacation_directory))
3414 filter->errmsg = CUS "unable to open vacation directory";
3422 while ((oncelog = readdir(oncelogdir)))
3423 if (strlen(oncelog->d_name) == 32)
3425 uschar *s = string_sprintf("%s/%s", filter->vacation_directory, oncelog->d_name);
3426 if (Ustat(s, &properties) == 0 && properties.st_mtime+VACATION_MAX_DAYS*86400 < now)
3429 closedir(oncelogdir);
3433 while (parse_identifier(filter, CUS "require"))
3436 require-command = "require" <capabilities: string-list>
3442 if (parse_white(filter) == -1) return -1;
3443 if ((m = parse_stringlist(filter, &cap)) != 1)
3445 if (m == 0) filter->errmsg = CUS "capability string list expected";
3448 for (gstring * check = cap; check->s; ++check)
3450 if (eq_octet(check, &str_envelope, FALSE)) filter->require_envelope = 1;
3451 else if (eq_octet(check, &str_fileinto, FALSE)) filter->require_fileinto = 1;
3452 #ifdef ENCODED_CHARACTER
3453 else if (eq_octet(check, &str_encoded_character, FALSE)) filter->require_encoded_character = TRUE;
3455 #ifdef ENVELOPE_AUTH
3456 else if (eq_octet(check, &str_envelope_auth, FALSE)) filter->require_envelope_auth = 1;
3459 else if (eq_octet(check, &str_enotify, FALSE))
3461 if (!filter->enotify_mailto_owner)
3463 filter->errmsg = CUS "enotify disabled";
3466 filter->require_enotify = 1;
3470 else if (eq_octet(check, &str_subaddress, FALSE)) filter->require_subaddress = TRUE;
3473 else if (eq_octet(check, &str_vacation, FALSE))
3475 if (filter_test == FTEST_NONE && !filter->vacation_directory)
3477 filter->errmsg = CUS "vacation disabled";
3480 filter->require_vacation = TRUE;
3483 else if (eq_octet(check, &str_copy, FALSE)) filter->require_copy = TRUE;
3484 else if (eq_octet(check, &str_comparator_ioctet, FALSE)) ;
3485 else if (eq_octet(check, &str_comparator_iascii_casemap, FALSE)) ;
3486 else if (eq_octet(check, &str_comparator_enascii_casemap, FALSE)) ;
3487 else if (eq_octet(check, &str_comparator_iascii_numeric, FALSE)) filter->require_iascii_numeric = TRUE;
3490 filter->errmsg = CUS "unknown capability";
3494 if (parse_semicolon(filter) == -1) return -1;
3496 if (parse_commands(filter, exec, generated) == -1) return -1;
3499 filter->errmsg = CUS "syntax error";
3506 /*************************************************
3507 * Interpret a sieve filter file *
3508 *************************************************/
3512 filter points to the entire file, read into store as a single string
3513 options controls whether various special things are allowed, and requests
3514 special actions (not currently used)
3516 vacation_directory where to store vacation "once" files
3517 enotify_mailto_owner owner of mailto notifications
3518 useraddress string expression for :user part of address
3519 subaddress string expression for :subaddress part of address
3520 inbox string expression for "keep"
3521 generated where to hang newly-generated addresses
3522 error where to pass back an error text
3524 Returns: FF_DELIVERED success, a significant action was taken
3525 FF_NOTDELIVERED success, no significant action
3526 FF_DEFER defer requested
3527 FF_FAIL fail requested
3528 FF_FREEZE freeze requested
3529 FF_ERROR there was a problem
3533 sieve_interpret(const uschar * filter, int options, const sieve_block * sb,
3534 address_item ** generated, uschar ** error)
3540 DEBUG(D_route) debug_printf_indent("Sieve: start of processing\n");
3542 sieve.filter = filter;
3544 GET_OPTION("sieve_vacation_directory");
3545 if (!sb || !sb->vacation_dir)
3546 sieve.vacation_directory = NULL;
3547 else if (!(sieve.vacation_directory = expand_cstring(sb->vacation_dir)))
3549 *error = string_sprintf("failed to expand \"%s\" "
3550 "(sieve_vacation_directory): %s", sb->vacation_dir, expand_string_message);
3554 GET_OPTION("sieve_vacation_directory");
3555 if (!sb || !sb->inbox)
3556 sieve.inbox = US"inbox";
3557 else if (!(sieve.inbox = expand_cstring(sb->inbox)))
3559 *error = string_sprintf("failed to expand \"%s\" "
3560 "(sieve_inbox): %s", sb->inbox, expand_string_message);
3564 GET_OPTION("sieve_enotify_mailto_owner");
3565 if (!sb || !sb->enotify_mailto_owner)
3566 sieve.enotify_mailto_owner = NULL;
3567 else if (!(sieve.enotify_mailto_owner = expand_cstring(sb->enotify_mailto_owner)))
3569 *error = string_sprintf("failed to expand \"%s\" "
3570 "(sieve_enotify_mailto_owner): %s", sb->enotify_mailto_owner,
3571 expand_string_message);
3575 GET_OPTION("sieve_useraddress");
3576 sieve.useraddress = sb && sb->useraddress
3577 ? sb->useraddress : CUS "$local_part_prefix$local_part$local_part_suffix";
3578 GET_OPTION("sieve_subaddress");
3579 sieve.subaddress = sb ? sb->subaddress : NULL;
3581 #ifdef COMPILE_SYNTAX_CHECKER
3582 if (parse_start(&sieve, 0, generated) == 1)
3584 if (parse_start(&sieve, 1, generated) == 1)
3588 add_addr(generated, sieve.inbox, 1, 0, 0, 0);
3589 msg = US"Implicit keep";
3594 msg = US"No implicit keep";
3599 msg = string_sprintf("Sieve error: %s in line %d", sieve.errmsg, sieve.line);
3600 #ifdef COMPILE_SYNTAX_CHECKER
3604 add_addr(generated, sieve.inbox, 1, 0, 0, 0);
3609 #ifndef COMPILE_SYNTAX_CHECKER
3610 if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
3611 else debug_printf_indent("%s\n", msg);
3615 DEBUG(D_route) debug_printf_indent("Sieve: end of processing\n");