1 /* $Cambridge: exim/src/src/sieve.c,v 1.13 2005/08/30 10:55:52 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) Michael Haardt 2003-2005 */
8 /* See the file NOTICE for conditions of use and distribution. */
10 /* This code was contributed by Michael Haardt. */
13 /* Sieve mail filter. */
27 /* Define this for RFC compliant \r\n end-of-line terminators. */
28 /* Undefine it for UNIX-style \n end-of-line terminators (default). */
31 /* Define this for development of the subaddress Sieve extension. */
34 /* Define this for the vacation Sieve extension. */
38 #define VACATION_MIN_DAYS 1
39 /* Must be >= VACATION_MIN_DAYS, must be > 7, should be > 30 */
40 #define VACATION_MAX_DAYS 31
42 /* Keep this at 75 to accept only RFC compliant MIME words. */
43 /* Increase it if you want to match headers from buggy MUAs. */
44 #define MIMEWORD_LENGTH 75
56 int require_subaddress;
62 uschar *vacation_directory;
63 const uschar *subaddress;
64 const uschar *useraddress;
66 int require_iascii_numeric;
69 enum Comparator { COMP_OCTET, COMP_EN_ASCII_CASEMAP, COMP_ASCII_NUMERIC };
70 enum MatchType { MATCH_IS, MATCH_CONTAINS, MATCH_MATCHES };
72 enum AddressPart { ADDRPART_USER, ADDRPART_DETAIL, ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
74 enum AddressPart { ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
76 enum RelOp { LT, LE, EQ, GE, GT, NE };
84 static int parse_test(struct Sieve *filter, int *cond, int exec);
85 static int parse_commands(struct Sieve *filter, int exec, address_item **generated);
87 static uschar str_from_c[]="From";
88 static const struct String str_from={ str_from_c, 4 };
89 static uschar str_to_c[]="To";
90 static const struct String str_to={ str_to_c, 2 };
91 static uschar str_cc_c[]="Cc";
92 static const struct String str_cc={ str_cc_c, 2 };
93 static uschar str_bcc_c[]="Bcc";
94 static const struct String str_bcc={ str_bcc_c, 3 };
95 static uschar str_sender_c[]="Sender";
96 static const struct String str_sender={ str_sender_c, 6 };
97 static uschar str_resent_from_c[]="Resent-From";
98 static const struct String str_resent_from={ str_resent_from_c, 11 };
99 static uschar str_resent_to_c[]="Resent-To";
100 static const struct String str_resent_to={ str_resent_to_c, 9 };
101 static uschar str_fileinto_c[]="fileinto";
102 static const struct String str_fileinto={ str_fileinto_c, 8 };
103 static uschar str_envelope_c[]="envelope";
104 static const struct String str_envelope={ str_envelope_c, 8 };
106 static uschar str_subaddress_c[]="subaddress";
107 static const struct String str_subaddress={ str_subaddress_c, 10 };
110 static uschar str_vacation_c[]="vacation";
111 static const struct String str_vacation={ str_vacation_c, 8 };
112 static uschar str_subject_c[]="Subject";
113 static const struct String str_subject={ str_subject_c, 7 };
115 static uschar str_copy_c[]="copy";
116 static const struct String str_copy={ str_copy_c, 4 };
117 static uschar str_iascii_casemap_c[]="i;ascii-casemap";
118 static const struct String str_iascii_casemap={ str_iascii_casemap_c, 15 };
119 static uschar str_enascii_casemap_c[]="en;ascii-casemap";
120 static const struct String str_enascii_casemap={ str_enascii_casemap_c, 16 };
121 static uschar str_ioctet_c[]="i;octet";
122 static const struct String str_ioctet={ str_ioctet_c, 7 };
123 static uschar str_iascii_numeric_c[]="i;ascii-numeric";
124 static const struct String str_iascii_numeric={ str_iascii_numeric_c, 15 };
125 static uschar str_comparator_iascii_casemap_c[]="comparator-i;ascii-casemap";
126 static const struct String str_comparator_iascii_casemap={ str_comparator_iascii_casemap_c, 26 };
127 static uschar str_comparator_enascii_casemap_c[]="comparator-en;ascii-casemap";
128 static const struct String str_comparator_enascii_casemap={ str_comparator_enascii_casemap_c, 27 };
129 static uschar str_comparator_ioctet_c[]="comparator-i;octet";
130 static const struct String str_comparator_ioctet={ str_comparator_ioctet_c, 18 };
131 static uschar str_comparator_iascii_numeric_c[]="comparator-i;ascii-numeric";
132 static const struct String str_comparator_iascii_numeric={ str_comparator_iascii_numeric_c, 26 };
135 /*************************************************
136 * Encode to quoted-printable *
137 *************************************************/
144 static struct String *quoted_printable_encode(const struct String *src, struct String *dst)
147 const uschar *start,*end;
152 for (pass=0; pass<=1; ++pass)
159 dst->character=store_get(dst->length+1); /* plus one for \0 */
162 for (start=src->character,end=start+src->length; start<end; ++start)
179 || (ch>=62 && ch<=126)
185 && (*(start+1)!='\r' || *(start+2)!='\n')
200 else if (ch=='\r' && start+1<end && *(start+1)=='\n')
228 sprintf(CS new,"=%02X",ch);
235 *new='\0'; /* not included in length, but nice */
240 /*************************************************
241 * Octet-wise string comparison *
242 *************************************************/
246 needle UTF-8 string to search ...
247 haystack ... inside the haystack
248 match_prefix 1 to compare if needle is a prefix of haystack
250 Returns: 0 needle not found in haystack
254 static int eq_octet(const struct String *needle,
255 const struct String *haystack, int match_prefix)
263 h=haystack->character;
267 if (*n&0x80) return 0;
268 if (*h&0x80) return 0;
270 if (*n!=*h) return 0;
276 return (match_prefix ? nl==0 : nl==0 && hl==0);
280 /*************************************************
281 * ASCII case-insensitive string comparison *
282 *************************************************/
286 needle UTF-8 string to search ...
287 haystack ... inside the haystack
288 match_prefix 1 to compare if needle is a prefix of haystack
290 Returns: 0 needle not found in haystack
294 static int eq_asciicase(const struct String *needle,
295 const struct String *haystack, int match_prefix)
304 h=haystack->character;
310 if (nc&0x80) return 0;
311 if (hc&0x80) return 0;
313 /* tolower depends on the locale and only ASCII case must be insensitive */
314 if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
320 return (match_prefix ? nl==0 : nl==0 && hl==0);
324 /*************************************************
325 * Glob pattern search *
326 *************************************************/
330 needle pattern to search ...
331 haystack ... inside the haystack
333 Returns: 0 needle not found in haystack
338 static int eq_glob(const struct String *needle,
339 const struct String *haystack, int ascii_caseless)
341 const uschar *n,*h,*nend,*hend;
345 h=haystack->character;
346 nend=n+needle->length;
347 hend=h+haystack->length;
357 const uschar *npart,*hpart;
359 /* Try to match a non-star part of the needle at the current */
360 /* position in the haystack. */
364 while (npart<nend && *npart!='*') switch (*npart)
368 if (hpart==hend) return 0;
369 /* watch out: Do not match one character, but one UTF8 encoded character */
370 if ((*hpart&0xc0)==0xc0)
373 while (hpart<hend && ((*hpart&0xc0)==0x80)) ++hpart;
383 if (npart==nend) return -1;
388 if (hpart==hend) return 0;
389 /* tolower depends on the locale, but we need ASCII */
393 (*hpart&0x80) || (*npart&0x80) ||
396 ? ((*npart>='A' && *npart<='Z' ? *npart|0x20 : *npart) != (*hpart>='A' && *hpart<='Z' ? *hpart|0x20 : *hpart))
401 /* string match after a star failed, advance and try again */
415 /* at this point, a part was matched successfully */
416 if (may_advance && npart==nend && hpart<hend)
417 /* needle ends, but haystack does not: if there was a star before, advance and try again */
427 return (h==hend ? 1 : may_advance);
431 /*************************************************
432 * ASCII numeric comparison *
433 *************************************************/
437 a first numeric string
438 b second numeric string
439 relop relational operator
441 Returns: 0 not (a relop b)
445 static int eq_asciinumeric(const struct String *a,
446 const struct String *b, enum RelOp relop)
449 const uschar *as,*aend,*bs,*bend;
453 aend=a->character+a->length;
455 bend=b->character+b->length;
457 while (*as>='0' && *as<='9' && as<aend) ++as;
459 while (*bs>='0' && *bs<='9' && bs<bend) ++bs;
462 if (al && bl==0) cmp=-1;
463 else if (al==0 && bl==0) cmp=0;
464 else if (al==0 && bl) cmp=1;
468 if (cmp==0) cmp=memcmp(a->character,b->character,al);
472 case LT: return cmp<0;
473 case LE: return cmp<=0;
474 case EQ: return cmp==0;
475 case GE: return cmp>=0;
476 case GT: return cmp>0;
477 case NE: return cmp!=0;
484 /*************************************************
486 *************************************************/
490 needle UTF-8 pattern or string to search ...
491 haystack ... inside the haystack
495 Returns: 0 needle not found in haystack
497 -1 comparator does not offer matchtype
500 static int compare(struct Sieve *filter, const struct String *needle, const struct String *haystack,
501 enum Comparator co, enum MatchType mt)
505 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
506 (debug_selector & D_filter) != 0)
508 debug_printf("String comparison (match ");
511 case MATCH_IS: debug_printf(":is"); break;
512 case MATCH_CONTAINS: debug_printf(":contains"); break;
513 case MATCH_MATCHES: debug_printf(":matches"); break;
515 debug_printf(", comparison \"");
518 case COMP_OCTET: debug_printf("i;octet"); break;
519 case COMP_EN_ASCII_CASEMAP: debug_printf("en;ascii-casemap"); break;
520 case COMP_ASCII_NUMERIC: debug_printf("i;ascii-numeric"); break;
522 debug_printf("\"):\n");
523 debug_printf(" Search = %s (%d chars)\n", needle->character,needle->length);
524 debug_printf(" Inside = %s (%d chars)\n", haystack->character,haystack->length);
534 if (eq_octet(needle,haystack,0)) r=1;
537 case COMP_EN_ASCII_CASEMAP:
539 if (eq_asciicase(needle,haystack,0)) r=1;
542 case COMP_ASCII_NUMERIC:
544 if (!filter->require_iascii_numeric)
546 filter->errmsg=CUS "missing previous require \"comparator-i;ascii-numeric\";";
549 if (eq_asciinumeric(needle,haystack,EQ)) r=1;
563 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_octet(needle,&h,1)) { r=1; break; }
566 case COMP_EN_ASCII_CASEMAP:
568 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_asciicase(needle,&h,1)) { r=1; break; }
573 filter->errmsg=CUS "comparator does not offer specified matchtype";
585 if ((r=eq_glob(needle,haystack,0))==-1)
587 filter->errmsg=CUS "syntactically invalid pattern";
592 case COMP_EN_ASCII_CASEMAP:
594 if ((r=eq_glob(needle,haystack,1))==-1)
596 filter->errmsg=CUS "syntactically invalid pattern";
603 filter->errmsg=CUS "comparator does not offer specified matchtype";
610 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
611 (debug_selector & D_filter) != 0)
612 debug_printf(" Result %s\n",r?"true":"false");
617 /*************************************************
618 * Check header field syntax *
619 *************************************************/
622 RFC 2822, section 3.6.8 says:
626 ftext = %d33-57 / ; Any character except
627 %d59-126 ; controls, SP, and
630 That forbids 8-bit header fields. This implementation accepts them, since
631 all of Exim is 8-bit clean, so it adds %d128-%d255.
634 header header field to quote for suitable use in Exim expansions
636 Returns: 0 string is not a valid header field
637 1 string is a value header field
640 static int is_header(const struct String *header)
650 if (((unsigned char)*h)<33 || ((unsigned char)*h)==':' || ((unsigned char)*h)==127) return 0;
661 /*************************************************
662 * Quote special characters string *
663 *************************************************/
667 header header field to quote for suitable use in Exim expansions
670 Returns: quoted string
673 static const uschar *quote(const struct String *header)
688 quoted=string_cat(quoted,&size,&ptr,CUS "\\0",2);
695 quoted=string_cat(quoted,&size,&ptr,CUS "\\",1);
699 quoted=string_cat(quoted,&size,&ptr,h,1);
705 quoted=string_cat(quoted,&size,&ptr,CUS "",1);
710 /*************************************************
711 * Add address to list of generated addresses *
712 *************************************************/
715 According to RFC 3028, duplicate delivery to the same address must
716 not happen, so the list is first searched for the address.
719 generated list of generated addresses
720 addr new address to add
721 file address denotes a file
726 static void add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
728 address_item *new_addr;
730 for (new_addr=*generated; new_addr; new_addr=new_addr->next)
732 if (Ustrcmp(new_addr->address,addr)==0 && (file ? testflag(new_addr, af_pfr|af_file) : 1))
734 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
736 debug_printf("Repeated %s `%s' ignored.\n",file ? "fileinto" : "redirect", addr);
742 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
744 debug_printf("%s `%s'\n",file ? "fileinto" : "redirect", addr);
746 new_addr=deliver_make_addr(addr,TRUE);
749 setflag(new_addr, af_pfr|af_file);
752 new_addr->p.errors_address = NULL;
753 new_addr->next = *generated;
754 *generated = new_addr;
758 /*************************************************
759 * Return decoded header field *
760 *************************************************/
764 value returned value of the field
765 header name of the header field
767 Returns: nothing The expanded string is empty
768 in case there is no such header
771 static void expand_header(struct String *value, const struct String *header)
777 value->character=(uschar*)0;
779 t=r=s=expand_string(string_sprintf("$rheader_%s",quote(header)));
786 while (*r==' ' || *r=='\t') ++r;
793 value->character=rfc2047_decode(s,TRUE,US"utf-8",'\0',&value->length,&errmsg);
797 /*************************************************
798 * Parse remaining hash comment *
799 *************************************************/
803 Comment up to terminating CRLF
806 filter points to the Sieve filter including its state
812 static int parse_hashcomment(struct Sieve *filter)
818 if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
820 if (*filter->pc=='\n')
833 filter->errmsg=CUS "missing end of comment";
838 /*************************************************
839 * Parse remaining C-style comment *
840 *************************************************/
844 Everything up to star slash
847 filter points to the Sieve filter including its state
853 static int parse_comment(struct Sieve *filter)
858 if (*filter->pc=='*' && *(filter->pc+1)=='/')
865 filter->errmsg=CUS "missing end of comment";
870 /*************************************************
871 * Parse optional white space *
872 *************************************************/
876 Spaces, tabs, CRLFs, hash comments or C-style comments
879 filter points to the Sieve filter including its state
885 static int parse_white(struct Sieve *filter)
889 if (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
891 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
893 else if (*filter->pc=='\n')
903 else if (*filter->pc=='#')
905 if (parse_hashcomment(filter)==-1) return -1;
907 else if (*filter->pc=='/' && *(filter->pc+1)=='*')
909 if (parse_comment(filter)==-1) return -1;
917 /*************************************************
918 * Parse a optional string *
919 *************************************************/
923 quoted-string = DQUOTE *CHAR DQUOTE
924 ;; in general, \ CHAR inside a string maps to CHAR
925 ;; so \" maps to " and \\ maps to \
926 ;; note that newlines and other characters are all allowed
929 multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
930 *(multi-line-literal / multi-line-dotstuff)
932 multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
933 multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
934 ;; A line containing only "." ends the multi-line.
935 ;; Remove a leading '.' if followed by another '.'.
936 string = quoted-string / multi-line
939 filter points to the Sieve filter including its state
940 id specifies identifier to match
944 0 identifier not matched
947 static int parse_string(struct Sieve *filter, struct String *data)
952 data->character=(uschar*)0;
953 if (*filter->pc=='"') /* quoted string */
958 if (*filter->pc=='"') /* end of string */
960 int foo=data->length;
963 data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
966 else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
968 if (*(filter->pc+1)=='0') data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
969 else data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc+1,1);
972 else /* regular character */
974 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
978 filter->errmsg=CUS "missing end of string";
981 else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
984 /* skip optional white space followed by hashed comment or CRLF */
985 while (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
986 if (*filter->pc=='#')
988 if (parse_hashcomment(filter)==-1) return -1;
991 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
993 else if (*filter->pc=='\n')
1005 filter->errmsg=CUS "syntax error";
1011 if (*filter->pc=='\r' && *(filter->pc+1)=='\n') /* end of line */
1013 if (*filter->pc=='\n') /* end of line */
1016 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
1024 if (*filter->pc=='.' && *(filter->pc+1)=='\r' && *(filter->pc+2)=='\n') /* end of string */
1026 if (*filter->pc=='.' && *(filter->pc+1)=='\n') /* end of string */
1029 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
1038 else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
1040 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS ".",1);
1044 else /* regular character */
1046 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1050 filter->errmsg=CUS "missing end of multi line string";
1057 /*************************************************
1058 * Parse a specific identifier *
1059 *************************************************/
1063 identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
1066 filter points to the Sieve filter including its state
1067 id specifies identifier to match
1070 0 identifier not matched
1073 static int parse_identifier(struct Sieve *filter, const uschar *id)
1075 size_t idlen=Ustrlen(id);
1077 if (Ustrncmp(filter->pc,id,idlen)==0)
1079 uschar next=filter->pc[idlen];
1081 if ((next>='A' && next<='Z') || (next>='a' && next<='z') || next=='_' || (next>='0' && next<='9')) return 0;
1089 /*************************************************
1091 *************************************************/
1095 number = 1*DIGIT [QUANTIFIER]
1096 QUANTIFIER = "K" / "M" / "G"
1099 filter points to the Sieve filter including its state
1103 -1 no string list found
1106 static int parse_number(struct Sieve *filter, unsigned long *data)
1110 if (*filter->pc>='0' && *filter->pc<='9')
1115 d=Ustrtoul(filter->pc,&e,10);
1118 filter->errmsg=CUstrerror(ERANGE);
1123 if (*filter->pc=='K') { u=1024; ++filter->pc; }
1124 else if (*filter->pc=='M') { u=1024*1024; ++filter->pc; }
1125 else if (*filter->pc=='G') { u=1024*1024*1024; ++filter->pc; }
1126 if (d>(ULONG_MAX/u))
1128 filter->errmsg=CUstrerror(ERANGE);
1137 filter->errmsg=CUS "missing number";
1143 /*************************************************
1144 * Parse a string list *
1145 *************************************************/
1149 string-list = "[" string *("," string) "]" / string
1152 filter points to the Sieve filter including its state
1153 data returns string list
1156 -1 no string list found
1159 static int parse_stringlist(struct Sieve *filter, struct String **data)
1161 const uschar *orig=filter->pc;
1164 struct String *d=(struct String*)0;
1167 if (*filter->pc=='[') /* string list */
1172 if (parse_white(filter)==-1) goto error;
1173 if ((dataLength+1)>=dataCapacity) /* increase buffer */
1176 int newCapacity; /* Don't amalgamate with next line; some compilers grumble */
1177 newCapacity=dataCapacity?(dataCapacity*=2):(dataCapacity=4);
1178 if ((new=(struct String*)store_get(sizeof(struct String)*newCapacity))==(struct String*)0)
1180 filter->errmsg=CUstrerror(errno);
1183 if (d) memcpy(new,d,sizeof(struct String)*dataLength);
1185 dataCapacity=newCapacity;
1187 m=parse_string(filter,&d[dataLength]);
1190 if (dataLength==0) break;
1193 filter->errmsg=CUS "missing string";
1197 else if (m==-1) goto error;
1199 if (parse_white(filter)==-1) goto error;
1200 if (*filter->pc==',') ++filter->pc;
1203 if (*filter->pc==']')
1205 d[dataLength].character=(uschar*)0;
1206 d[dataLength].length=-1;
1213 filter->errmsg=CUS "missing closing bracket";
1217 else /* single string */
1219 if ((d=store_get(sizeof(struct String)*2))==(struct String*)0)
1223 m=parse_string(filter,&d[0]);
1235 d[1].character=(uschar*)0;
1242 filter->errmsg=CUS "missing string list";
1247 /*************************************************
1248 * Parse an optional address part specifier *
1249 *************************************************/
1253 address-part = ":localpart" / ":domain" / ":all"
1254 address-part =/ ":user" / ":detail"
1257 filter points to the Sieve filter including its state
1258 a returns address part specified
1261 0 no comparator found
1265 static int parse_addresspart(struct Sieve *filter, enum AddressPart *a)
1268 if (parse_identifier(filter,CUS ":user")==1)
1270 if (!filter->require_subaddress)
1272 filter->errmsg=CUS "missing previous require \"subaddress\";";
1278 else if (parse_identifier(filter,CUS ":detail")==1)
1280 if (!filter->require_subaddress)
1282 filter->errmsg=CUS "missing previous require \"subaddress\";";
1290 if (parse_identifier(filter,CUS ":localpart")==1)
1292 *a=ADDRPART_LOCALPART;
1295 else if (parse_identifier(filter,CUS ":domain")==1)
1300 else if (parse_identifier(filter,CUS ":all")==1)
1309 /*************************************************
1310 * Parse an optional comparator *
1311 *************************************************/
1315 comparator = ":comparator" <comparator-name: string>
1318 filter points to the Sieve filter including its state
1319 c returns comparator
1322 0 no comparator found
1323 -1 incomplete comparator found
1326 static int parse_comparator(struct Sieve *filter, enum Comparator *c)
1328 struct String comparator_name;
1330 if (parse_identifier(filter,CUS ":comparator")==0) return 0;
1331 if (parse_white(filter)==-1) return -1;
1332 switch (parse_string(filter,&comparator_name))
1337 filter->errmsg=CUS "missing comparator";
1344 if (eq_asciicase(&comparator_name,&str_ioctet,0))
1349 else if (eq_asciicase(&comparator_name,&str_iascii_casemap,0))
1351 *c=COMP_EN_ASCII_CASEMAP;
1354 else if (eq_asciicase(&comparator_name,&str_enascii_casemap,0))
1356 *c=COMP_EN_ASCII_CASEMAP;
1359 else if (eq_asciicase(&comparator_name,&str_iascii_numeric,0))
1361 *c=COMP_ASCII_NUMERIC;
1366 filter->errmsg=CUS "invalid comparator";
1375 /*************************************************
1376 * Parse an optional match type *
1377 *************************************************/
1381 match-type = ":is" / ":contains" / ":matches"
1384 filter points to the Sieve filter including its state
1385 m returns match type
1388 0 no match type found
1391 static int parse_matchtype(struct Sieve *filter, enum MatchType *m)
1393 if (parse_identifier(filter,CUS ":is")==1)
1398 else if (parse_identifier(filter,CUS ":contains")==1)
1403 else if (parse_identifier(filter,CUS ":matches")==1)
1412 /*************************************************
1413 * Parse and interpret an optional test list *
1414 *************************************************/
1418 test-list = "(" test *("," test) ")"
1421 filter points to the Sieve filter including its state
1422 n total number of tests
1423 true number of passed tests
1424 exec Execute parsed statements
1427 0 no test list found
1428 -1 syntax or execution error
1431 static int parse_testlist(struct Sieve *filter, int *n, int *true, int exec)
1433 if (parse_white(filter)==-1) return -1;
1434 if (*filter->pc=='(')
1443 switch (parse_test(filter,&cond,exec))
1446 case 0: filter->errmsg=CUS "missing test"; return -1;
1447 default: ++*n; if (cond) ++*true; break;
1449 if (parse_white(filter)==-1) return -1;
1450 if (*filter->pc==',') ++filter->pc;
1453 if (*filter->pc==')')
1460 filter->errmsg=CUS "missing closing paren";
1468 /*************************************************
1469 * Parse and interpret an optional test *
1470 *************************************************/
1474 filter points to the Sieve filter including its state
1475 cond returned condition status
1476 exec Execute parsed statements
1480 -1 syntax or execution error
1483 static int parse_test(struct Sieve *filter, int *cond, int exec)
1485 if (parse_white(filter)==-1) return -1;
1486 if (parse_identifier(filter,CUS "address"))
1489 address-test = "address" { [address-part] [comparator] [match-type] }
1490 <header-list: string-list> <key-list: string-list>
1492 header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
1495 enum AddressPart addressPart=ADDRPART_ALL;
1496 enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
1497 enum MatchType matchType=MATCH_IS;
1498 struct String *hdr,*h,*key,*k;
1504 if (parse_white(filter)==-1) return -1;
1505 if ((m=parse_addresspart(filter,&addressPart))!=0)
1507 if (m==-1) return -1;
1510 filter->errmsg=CUS "address part already specified";
1515 else if ((m=parse_comparator(filter,&comparator))!=0)
1517 if (m==-1) return -1;
1520 filter->errmsg=CUS "comparator already specified";
1525 else if ((m=parse_matchtype(filter,&matchType))!=0)
1527 if (m==-1) return -1;
1530 filter->errmsg=CUS "match type already specified";
1537 if (parse_white(filter)==-1) return -1;
1538 if ((m=parse_stringlist(filter,&hdr))!=1)
1540 if (m==0) filter->errmsg=CUS "header string list expected";
1543 if (parse_white(filter)==-1) return -1;
1544 if ((m=parse_stringlist(filter,&key))!=1)
1546 if (m==0) filter->errmsg=CUS "key string list expected";
1550 for (h=hdr; h->length!=-1 && !*cond; ++h)
1552 uschar *header_value=(uschar*)0,*extracted_addr,*end_addr;
1556 !eq_asciicase(h,&str_from,0)
1557 && !eq_asciicase(h,&str_to,0)
1558 && !eq_asciicase(h,&str_cc,0)
1559 && !eq_asciicase(h,&str_bcc,0)
1560 && !eq_asciicase(h,&str_sender,0)
1561 && !eq_asciicase(h,&str_resent_from,0)
1562 && !eq_asciicase(h,&str_resent_to,0)
1565 filter->errmsg=CUS "invalid header field";
1570 /* We are only interested in addresses below, so no MIME decoding */
1571 header_value=expand_string(string_sprintf("$rheader_%s",quote(h)));
1572 if (header_value == NULL)
1574 filter->errmsg=CUS "header string expansion failed";
1577 parse_allow_group = TRUE;
1578 while (*header_value && !*cond)
1581 int start, end, domain;
1585 end_addr = parse_find_address_end(header_value, FALSE);
1586 saveend = *end_addr;
1588 extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
1590 if (extracted_addr) switch (addressPart)
1592 case ADDRPART_ALL: part=extracted_addr; break;
1596 case ADDRPART_LOCALPART: part=extracted_addr; part[domain-1]='\0'; break;
1597 case ADDRPART_DOMAIN: part=extracted_addr+domain; break;
1599 case ADDRPART_DETAIL: part=NULL; break;
1603 *end_addr = saveend;
1606 for (k=key; k->length!=-1; ++k)
1608 struct String partStr;
1610 partStr.character=part;
1611 partStr.length=Ustrlen(part);
1614 *cond=compare(filter,k,&partStr,comparator,matchType);
1615 if (*cond==-1) return -1;
1620 if (saveend == 0) break;
1621 header_value = end_addr + 1;
1627 else if (parse_identifier(filter,CUS "allof"))
1630 allof-test = "allof" <tests: test-list>
1635 switch (parse_testlist(filter,&n,&true,exec))
1638 case 0: filter->errmsg=CUS "missing test list"; return -1;
1639 default: *cond=(n==true); return 1;
1642 else if (parse_identifier(filter,CUS "anyof"))
1645 anyof-test = "anyof" <tests: test-list>
1650 switch (parse_testlist(filter,&n,&true,exec))
1653 case 0: filter->errmsg=CUS "missing test list"; return -1;
1654 default: *cond=(true>0); return 1;
1657 else if (parse_identifier(filter,CUS "exists"))
1660 exists-test = "exists" <header-names: string-list>
1663 struct String *hdr,*h;
1666 if (parse_white(filter)==-1) return -1;
1667 if ((m=parse_stringlist(filter,&hdr))!=1)
1669 if (m==0) filter->errmsg=CUS "header string list expected";
1675 for (h=hdr; h->length!=-1 && *cond; ++h)
1679 header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
1680 if (header_def == NULL)
1682 filter->errmsg=CUS "header string expansion failed";
1685 if (Ustrcmp(header_def,"false")==0) *cond=0;
1690 else if (parse_identifier(filter,CUS "false"))
1693 false-test = "false"
1699 else if (parse_identifier(filter,CUS "header"))
1702 header-test = "header" { [comparator] [match-type] }
1703 <header-names: string-list> <key-list: string-list>
1706 enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
1707 enum MatchType matchType=MATCH_IS;
1708 struct String *hdr,*h,*key,*k;
1714 if (parse_white(filter)==-1) return -1;
1715 if ((m=parse_comparator(filter,&comparator))!=0)
1717 if (m==-1) return -1;
1720 filter->errmsg=CUS "comparator already specified";
1725 else if ((m=parse_matchtype(filter,&matchType))!=0)
1727 if (m==-1) return -1;
1730 filter->errmsg=CUS "match type already specified";
1737 if (parse_white(filter)==-1) return -1;
1738 if ((m=parse_stringlist(filter,&hdr))!=1)
1740 if (m==0) filter->errmsg=CUS "header string list expected";
1743 if (parse_white(filter)==-1) return -1;
1744 if ((m=parse_stringlist(filter,&key))!=1)
1746 if (m==0) filter->errmsg=CUS "key string list expected";
1750 for (h=hdr; h->length!=-1 && !*cond; ++h)
1754 filter->errmsg=CUS "invalid header field";
1759 struct String header_value;
1762 expand_header(&header_value,h);
1763 header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
1764 if (header_value.character == NULL || header_def == NULL)
1766 filter->errmsg=CUS "header string expansion failed";
1769 for (k=key; k->length!=-1; ++k)
1771 if (Ustrcmp(header_def,"true")==0)
1773 *cond=compare(filter,k,&header_value,comparator,matchType);
1774 if (*cond==-1) return -1;
1782 else if (parse_identifier(filter,CUS "not"))
1784 if (parse_white(filter)==-1) return -1;
1785 switch (parse_test(filter,cond,exec))
1788 case 0: filter->errmsg=CUS "missing test"; return -1;
1789 default: *cond=!*cond; return 1;
1792 else if (parse_identifier(filter,CUS "size"))
1795 relop = ":over" / ":under"
1796 size-test = "size" relop <limit: number>
1799 unsigned long limit;
1802 if (parse_white(filter)==-1) return -1;
1803 if (parse_identifier(filter,CUS ":over")) overNotUnder=1;
1804 else if (parse_identifier(filter,CUS ":under")) overNotUnder=0;
1807 filter->errmsg=CUS "missing :over or :under";
1810 if (parse_white(filter)==-1) return -1;
1811 if (parse_number(filter,&limit)==-1) return -1;
1812 *cond=(overNotUnder ? (message_size>limit) : (message_size<limit));
1815 else if (parse_identifier(filter,CUS "true"))
1820 else if (parse_identifier(filter,CUS "envelope"))
1823 envelope-test = "envelope" { [comparator] [address-part] [match-type] }
1824 <envelope-part: string-list> <key-list: string-list>
1826 envelope-part is case insensitive "from" or "to"
1829 enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
1830 enum AddressPart addressPart=ADDRPART_ALL;
1831 enum MatchType matchType=MATCH_IS;
1832 struct String *env,*e,*key,*k;
1836 if (!filter->require_envelope)
1838 filter->errmsg=CUS "missing previous require \"envelope\";";
1843 if (parse_white(filter)==-1) return -1;
1844 if ((m=parse_comparator(filter,&comparator))!=0)
1846 if (m==-1) return -1;
1849 filter->errmsg=CUS "comparator already specified";
1854 else if ((m=parse_addresspart(filter,&addressPart))!=0)
1856 if (m==-1) return -1;
1859 filter->errmsg=CUS "address part already specified";
1864 else if ((m=parse_matchtype(filter,&matchType))!=0)
1866 if (m==-1) return -1;
1869 filter->errmsg=CUS "match type already specified";
1876 if (parse_white(filter)==-1) return -1;
1877 if ((m=parse_stringlist(filter,&env))!=1)
1879 if (m==0) filter->errmsg=CUS "envelope string list expected";
1882 if (parse_white(filter)==-1) return -1;
1883 if ((m=parse_stringlist(filter,&key))!=1)
1885 if (m==0) filter->errmsg=CUS "key string list expected";
1889 for (e=env; e->character; ++e)
1891 const uschar *envelopeExpr=CUS 0;
1892 uschar *envelope=US 0;
1894 if (eq_asciicase(e,&str_from,0))
1896 switch (addressPart)
1898 case ADDRPART_ALL: envelopeExpr=CUS "$sender_address"; break;
1902 case ADDRPART_LOCALPART: envelopeExpr=CUS "${local_part:$sender_address}"; break;
1903 case ADDRPART_DOMAIN: envelopeExpr=CUS "${domain:$sender_address}"; break;
1905 case ADDRPART_DETAIL: envelopeExpr=CUS 0; break;
1909 else if (eq_asciicase(e,&str_to,0))
1911 switch (addressPart)
1913 case ADDRPART_ALL: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
1915 case ADDRPART_USER: envelopeExpr=filter->useraddress; break;
1916 case ADDRPART_DETAIL: envelopeExpr=filter->subaddress; break;
1918 case ADDRPART_LOCALPART: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix"; break;
1919 case ADDRPART_DOMAIN: envelopeExpr=CUS "$domain"; break;
1924 filter->errmsg=CUS "invalid envelope string";
1927 if (exec && envelopeExpr)
1929 if ((envelope=expand_string(US envelopeExpr)) == NULL)
1931 filter->errmsg=CUS "header string expansion failed";
1934 for (k=key; k->length!=-1; ++k)
1936 struct String envelopeStr;
1938 envelopeStr.character=envelope;
1939 envelopeStr.length=Ustrlen(envelope);
1940 *cond=compare(filter,k,&envelopeStr,comparator,matchType);
1941 if (*cond==-1) return -1;
1952 /*************************************************
1953 * Parse and interpret an optional block *
1954 *************************************************/
1958 filter points to the Sieve filter including its state
1959 exec Execute parsed statements
1960 generated where to hang newly-generated addresses
1962 Returns: 2 success by stop
1964 0 no block command found
1965 -1 syntax or execution error
1968 static int parse_block(struct Sieve *filter, int exec,
1969 address_item **generated)
1973 if (parse_white(filter)==-1) return -1;
1974 if (*filter->pc=='{')
1977 if ((r=parse_commands(filter,exec,generated))==-1 || r==2) return r;
1978 if (*filter->pc=='}')
1985 filter->errmsg=CUS "expecting command or closing brace";
1993 /*************************************************
1994 * Match a semicolon *
1995 *************************************************/
1999 filter points to the Sieve filter including its state
2005 static int parse_semicolon(struct Sieve *filter)
2007 if (parse_white(filter)==-1) return -1;
2008 if (*filter->pc==';')
2015 filter->errmsg=CUS "missing semicolon";
2021 /*************************************************
2022 * Parse and interpret a Sieve command *
2023 *************************************************/
2027 filter points to the Sieve filter including its state
2028 exec Execute parsed statements
2029 generated where to hang newly-generated addresses
2031 Returns: 2 success by stop
2033 -1 syntax or execution error
2035 static int parse_commands(struct Sieve *filter, int exec,
2036 address_item **generated)
2040 if (parse_white(filter)==-1) return -1;
2041 if (parse_identifier(filter,CUS "if"))
2044 if-command = "if" test block *( "elsif" test block ) [ else block ]
2047 int cond,m,unsuccessful;
2050 if (parse_white(filter)==-1) return -1;
2051 if ((m=parse_test(filter,&cond,exec))==-1) return -1;
2054 filter->errmsg=CUS "missing test";
2057 m=parse_block(filter,exec ? cond : 0, generated);
2058 if (m==-1 || m==2) return m;
2061 filter->errmsg=CUS "missing block";
2064 unsuccessful = !cond;
2065 for (;;) /* elsif test block */
2067 if (parse_white(filter)==-1) return -1;
2068 if (parse_identifier(filter,CUS "elsif"))
2070 if (parse_white(filter)==-1) return -1;
2071 m=parse_test(filter,&cond,exec && unsuccessful);
2072 if (m==-1 || m==2) return m;
2075 filter->errmsg=CUS "missing test";
2078 m=parse_block(filter,exec && unsuccessful ? cond : 0, generated);
2079 if (m==-1 || m==2) return m;
2082 filter->errmsg=CUS "missing block";
2085 if (exec && unsuccessful && cond) unsuccessful = 0;
2090 if (parse_white(filter)==-1) return -1;
2091 if (parse_identifier(filter,CUS "else"))
2093 m=parse_block(filter,exec && unsuccessful, generated);
2094 if (m==-1 || m==2) return m;
2097 filter->errmsg=CUS "missing block";
2102 else if (parse_identifier(filter,CUS "stop"))
2105 stop-command = "stop" { stop-options } ";"
2109 if (parse_semicolon(filter)==-1) return -1;
2112 filter->pc+=Ustrlen(filter->pc);
2116 else if (parse_identifier(filter,CUS "keep"))
2119 keep-command = "keep" { keep-options } ";"
2123 if (parse_semicolon(filter)==-1) return -1;
2126 add_addr(generated,US"inbox",1,0,0,0);
2130 else if (parse_identifier(filter,CUS "discard"))
2133 discard-command = "discard" { discard-options } ";"
2137 if (parse_semicolon(filter)==-1) return -1;
2138 if (exec) filter->keep=0;
2140 else if (parse_identifier(filter,CUS "redirect"))
2143 redirect-command = "redirect" redirect-options "string" ";"
2145 redirect-options =) ":copy"
2148 struct String recipient;
2154 if (parse_white(filter)==-1) return -1;
2155 if (parse_identifier(filter,CUS ":copy")==1)
2157 if (!filter->require_copy)
2159 filter->errmsg=CUS "missing previous require \"copy\";";
2166 if (parse_white(filter)==-1) return -1;
2167 if ((m=parse_string(filter,&recipient))!=1)
2169 if (m==0) filter->errmsg=CUS "missing redirect recipient string";
2172 if (strchr(CCS recipient.character,'@')==(char*)0)
2174 filter->errmsg=CUS "unqualified recipient address";
2179 add_addr(generated,recipient.character,0,0,0,0);
2180 if (!copy) filter->keep = 0;
2182 if (parse_semicolon(filter)==-1) return -1;
2184 else if (parse_identifier(filter,CUS "fileinto"))
2187 fileinto-command = "fileinto" { fileinto-options } string ";"
2189 fileinto-options =) [ ":copy" ]
2192 struct String folder;
2195 unsigned long maxage, maxmessages, maxstorage;
2198 maxage = maxmessages = maxstorage = 0;
2199 if (!filter->require_fileinto)
2201 filter->errmsg=CUS "missing previous require \"fileinto\";";
2206 if (parse_white(filter)==-1) return -1;
2207 if (parse_identifier(filter,CUS ":copy")==1)
2209 if (!filter->require_copy)
2211 filter->errmsg=CUS "missing previous require \"copy\";";
2218 if (parse_white(filter)==-1) return -1;
2219 if ((m=parse_string(filter,&folder))!=1)
2221 if (m==0) filter->errmsg=CUS "missing fileinto folder string";
2224 m=0; s=folder.character;
2225 if (folder.length==0) m=1;
2226 if (Ustrcmp(s,"..")==0 || Ustrncmp(s,"../",3)==0) m=1;
2229 if (Ustrcmp(s,"/..")==0 || Ustrncmp(s,"/../",4)==0) { m=1; break; }
2234 filter->errmsg=CUS "invalid folder";
2239 add_addr(generated, folder.character, 1, maxage, maxmessages, maxstorage);
2240 if (!copy) filter->keep = 0;
2242 if (parse_semicolon(filter)==-1) return -1;
2245 else if (parse_identifier(filter,CUS "vacation"))
2248 vacation-command = "vacation" { vacation-options } <reason: string> ";"
2249 vacation-options = [":days" number]
2252 [":addresses" string-list]
2259 struct String subject;
2261 struct String *addresses;
2263 string_item *aliases;
2264 struct String handle;
2265 struct String reason;
2267 if (!filter->require_vacation)
2269 filter->errmsg=CUS "missing previous require \"vacation\";";
2274 if (filter->vacation_ran)
2276 filter->errmsg=CUS "trying to execute vacation more than once";
2279 filter->vacation_ran=1;
2281 days=VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
2282 subject.character=(uschar*)0;
2284 from.character=(uschar*)0;
2286 addresses=(struct String*)0;
2289 handle.character=(uschar*)0;
2293 if (parse_white(filter)==-1) return -1;
2294 if (parse_identifier(filter,CUS ":days")==1)
2296 if (parse_white(filter)==-1) return -1;
2297 if (parse_number(filter,&days)==-1) return -1;
2298 if (days<VACATION_MIN_DAYS) days=VACATION_MIN_DAYS;
2299 else if (days>VACATION_MAX_DAYS) days=VACATION_MAX_DAYS;
2301 else if (parse_identifier(filter,CUS ":subject")==1)
2303 if (parse_white(filter)==-1) return -1;
2304 if ((m=parse_string(filter,&subject))!=1)
2306 if (m==0) filter->errmsg=CUS "subject string expected";
2310 else if (parse_identifier(filter,CUS ":from")==1)
2312 int start, end, domain;
2315 if (parse_white(filter)==-1) return -1;
2316 if ((m=parse_string(filter,&from))!=1)
2318 if (m==0) filter->errmsg=CUS "from string expected";
2323 ss = parse_extract_address(from.character, &error, &start, &end, &domain,
2327 filter->errmsg=string_sprintf("malformed address \"%s\" in "
2328 "Sieve filter: %s", from.character, error);
2334 filter->errmsg=CUS "empty :from address in Sieve filter";
2338 else if (parse_identifier(filter,CUS ":addresses")==1)
2342 if (parse_white(filter)==-1) return -1;
2343 if ((m=parse_stringlist(filter,&addresses))!=1)
2345 if (m==0) filter->errmsg=CUS "addresses string list expected";
2348 for (a=addresses; a->length!=-1; ++a)
2352 new=store_get(sizeof(string_item));
2353 new->text=store_get(a->length+1);
2354 if (a->length) memcpy(new->text,a->character,a->length);
2355 new->text[a->length]='\0';
2360 else if (parse_identifier(filter,CUS ":mime")==1)
2362 else if (parse_identifier(filter,CUS ":handle")==1)
2364 if (parse_white(filter)==-1) return -1;
2365 if ((m=parse_string(filter,&from))!=1)
2367 if (m==0) filter->errmsg=CUS "handle string expected";
2373 if (parse_white(filter)==-1) return -1;
2374 if ((m=parse_string(filter,&reason))!=1)
2376 if (m==0) filter->errmsg=CUS "missing reason string";
2383 for (s=reason.character,end=reason.character+reason.length; s<end && (*s&0x80)==0; ++s);
2386 filter->errmsg=CUS "MIME reason string contains 8bit text";
2390 if (parse_semicolon(filter)==-1) return -1;
2397 int buffer_capacity;
2401 uschar hexdigest[33];
2405 if (filter_personal(aliases,TRUE))
2407 if (filter_test == FTEST_NONE)
2409 /* ensure oncelog directory exists; failure will be detected later */
2411 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
2413 /* build oncelog filename */
2415 key.character=(uschar*)0;
2418 if (handle.length==-1)
2420 if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
2421 if (from.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,from.character,from.length);
2422 key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
2423 key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
2428 md5_end(&base, key.character, key.length, digest);
2429 for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
2430 if (filter_test != FTEST_NONE)
2432 debug_printf("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
2436 capacity=Ustrlen(filter->vacation_directory);
2438 once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
2439 once=string_cat(once,&capacity,&start,hexdigest,33);
2442 /* process subject */
2444 if (subject.length==-1)
2446 expand_header(&subject,&str_subject);
2449 subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
2450 subject.length=start;
2453 /* add address to list of generated addresses */
2455 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
2456 setflag(addr, af_pfr);
2457 setflag(addr, af_ignore_error);
2458 addr->next = *generated;
2460 addr->reply = store_get(sizeof(reply_item));
2461 memset(addr->reply,0,sizeof(reply_item)); /* XXX */
2462 addr->reply->to = string_copy(sender_address);
2463 if (from.length==-1)
2464 addr->reply->from = expand_string(US"$local_part@$domain");
2466 addr->reply->from = from.character;
2467 /* Allocation is larger than neccessary, but enough even for split MIME words */
2468 buffer_capacity=16+4*subject.length;
2469 buffer=store_get(buffer_capacity);
2470 addr->reply->subject=parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity);
2471 addr->reply->oncelog=once;
2472 addr->reply->once_repeat=days*86400;
2474 /* build body and MIME headers */
2478 uschar *mime_body,*reason_end;
2480 static const uschar nlnl[]="\r\n\r\n";
2482 static const uschar nlnl[]="\n\n";
2487 mime_body=reason.character,reason_end=reason.character+reason.length;
2488 mime_body<(reason_end-sizeof(nlnl)-1) && memcmp(mime_body,nlnl,sizeof(nlnl)-1);
2493 addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
2494 addr->reply->headers[start] = '\0';
2497 if (mime_body+(sizeof(nlnl)-1)<reason_end) mime_body+=sizeof(nlnl)-1;
2498 else mime_body=reason_end-1;
2499 addr->reply->text = string_cat(NULL,&capacity,&start,mime_body,reason_end-mime_body);
2500 addr->reply->text[start] = '\0';
2507 start = reason.length;
2508 addr->reply->headers = US"MIME-Version: 1.0\n"
2509 "Content-Type: text/plain;\n"
2510 "\tcharset=\"utf-8\"\n"
2511 "Content-Transfer-Encoding: quoted-printable";
2512 addr->reply->text = quoted_printable_encode(&reason,&qp)->character;
2516 else if (filter_test != FTEST_NONE)
2518 debug_printf("Sieve: mail was not personal, vacation would ignore it\n");
2529 /*************************************************
2530 * Parse and interpret a sieve filter *
2531 *************************************************/
2535 filter points to the Sieve filter including its state
2536 exec Execute parsed statements
2537 generated where to hang newly-generated addresses
2540 -1 syntax or execution error
2543 static int parse_start(struct Sieve *filter, int exec,
2544 address_item **generated)
2546 filter->pc=filter->filter;
2549 filter->require_envelope=0;
2550 filter->require_fileinto=0;
2552 filter->require_subaddress=0;
2555 filter->require_vacation=0;
2556 filter->vacation_ran=0;
2558 filter->require_copy=0;
2559 filter->require_iascii_numeric=0;
2561 if (parse_white(filter)==-1) return -1;
2563 if (exec && filter->vacation_directory != NULL && filter_test == FTEST_NONE)
2566 struct dirent *oncelog;
2567 struct stat properties;
2570 /* clean up old vacation log databases */
2572 oncelogdir=opendir(CS filter->vacation_directory);
2574 if (oncelogdir ==(DIR*)0 && errno != ENOENT)
2576 filter->errmsg=CUS "unable to open vacation directory";
2580 if (oncelogdir != NULL)
2584 while ((oncelog=readdir(oncelogdir))!=(struct dirent*)0)
2586 if (strlen(oncelog->d_name)==32)
2588 uschar *s=string_sprintf("%s/%s",filter->vacation_directory,oncelog->d_name);
2589 if (Ustat(s,&properties)==0 && (properties.st_mtime+VACATION_MAX_DAYS*86400)<now)
2593 closedir(oncelogdir);
2597 while (parse_identifier(filter,CUS "require"))
2600 require-command = "require" <capabilities: string-list>
2603 struct String *cap,*check;
2606 if (parse_white(filter)==-1) return -1;
2607 if ((m=parse_stringlist(filter,&cap))!=1)
2609 if (m==0) filter->errmsg=CUS "capability string list expected";
2612 for (check=cap; check->character; ++check)
2614 if (eq_octet(check,&str_envelope,0)) filter->require_envelope=1;
2615 else if (eq_octet(check,&str_fileinto,0)) filter->require_fileinto=1;
2617 else if (eq_octet(check,&str_subaddress,0)) filter->require_subaddress=1;
2620 else if (eq_octet(check,&str_vacation,0))
2622 if (filter_test == FTEST_NONE && filter->vacation_directory == NULL)
2624 filter->errmsg=CUS "vacation disabled";
2627 filter->require_vacation=1;
2630 else if (eq_octet(check,&str_copy,0)) filter->require_copy=1;
2631 else if (eq_octet(check,&str_comparator_ioctet,0)) ;
2632 else if (eq_octet(check,&str_comparator_iascii_casemap,0)) ;
2633 else if (eq_octet(check,&str_comparator_enascii_casemap,0)) ;
2634 else if (eq_octet(check,&str_comparator_iascii_numeric,0)) filter->require_iascii_numeric=1;
2637 filter->errmsg=CUS "unknown capability";
2641 if (parse_semicolon(filter)==-1) return -1;
2643 if (parse_commands(filter,exec,generated)==-1) return -1;
2646 filter->errmsg=CUS "syntax error";
2653 /*************************************************
2654 * Interpret a sieve filter file *
2655 *************************************************/
2659 filter points to the entire file, read into store as a single string
2660 options controls whether various special things are allowed, and requests
2661 special actions (not currently used)
2662 sieve_vacation_directory where to store vacation "once" files
2663 useraddress string expression for :user part of address
2664 subaddress string expression for :subaddress part of address
2665 generated where to hang newly-generated addresses
2666 error where to pass back an error text
2668 Returns: FF_DELIVERED success, a significant action was taken
2669 FF_NOTDELIVERED success, no significant action
2670 FF_DEFER defer requested
2671 FF_FAIL fail requested
2672 FF_FREEZE freeze requested
2673 FF_ERROR there was a problem
2677 sieve_interpret(uschar *filter, int options, uschar *vacation_directory,
2678 uschar *useraddress, uschar *subaddress, address_item **generated, uschar **error)
2684 options = options; /* Keep picky compilers happy */
2687 DEBUG(D_route) debug_printf("Sieve: start of processing\n");
2688 sieve.filter=filter;
2690 if (vacation_directory == NULL)
2691 sieve.vacation_directory = NULL;
2694 sieve.vacation_directory=expand_string(vacation_directory);
2695 if (sieve.vacation_directory == NULL)
2697 *error = string_sprintf("failed to expand \"%s\" "
2698 "(sieve_vacation_directory): %s", vacation_directory,
2699 expand_string_message);
2704 sieve.useraddress = useraddress == NULL ? CUS "$local_part_prefix$local_part$local_part_suffix" : useraddress;
2705 sieve.subaddress = subaddress;
2707 #ifdef COMPILE_SYNTAX_CHECKER
2708 if (parse_start(&sieve,0,generated)==1)
2710 if (parse_start(&sieve,1,generated)==1)
2715 add_addr(generated,US"inbox",1,0,0,0);
2716 msg = string_sprintf("Implicit keep");
2721 msg = string_sprintf("No implicit keep");
2727 msg = string_sprintf("Sieve error: %s in line %d",sieve.errmsg,sieve.line);
2728 #ifdef COMPILE_SYNTAX_CHECKER
2732 add_addr(generated,US"inbox",1,0,0,0);
2737 #ifndef COMPILE_SYNTAX_CHECKER
2738 if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
2739 else debug_printf("%s\n", msg);
2742 DEBUG(D_route) debug_printf("Sieve: end of processing\n");