1 /* $Cambridge: exim/src/src/sieve.c,v 1.2 2004/11/25 13:54:31 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) Michael Haardt 2003,2004 */
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. */
32 /* The code is currently broken. */
35 /* Define this for development of the vacation Sieve extension. */
36 /* The code is not yet finished. */
40 #define VACATION_MIN_DAYS 1
41 /* Must be >= VACATION_MIN_DAYS, must be > 7, should be > 30 */
42 #define VACATION_MAX_DAYS 31
44 /* Keep this at 75 to accept only RFC compliant MIME words. */
45 /* Increase it if you want to match headers from buggy MUAs. */
46 #define MIMEWORD_LENGTH 75
58 int require_subaddress;
64 uschar *vacation_directory;
66 int require_iascii_numeric;
69 enum Comparator { COMP_OCTET, COMP_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_ioctet_c[]="i;octet";
120 static const struct String str_ioctet={ str_ioctet_c, 7 };
121 static uschar str_iascii_numeric_c[]="i;ascii-numeric";
122 static const struct String str_iascii_numeric={ str_iascii_numeric_c, 15 };
123 static uschar str_comparator_iascii_casemap_c[]="comparator-i;ascii-casemap";
124 static const struct String str_comparator_iascii_casemap={ str_comparator_iascii_casemap_c, 26 };
125 static uschar str_comparator_ioctet_c[]="comparator-i;octet";
126 static const struct String str_comparator_ioctet={ str_comparator_ioctet_c, 18 };
127 static uschar str_comparator_iascii_numeric_c[]="comparator-i;ascii-numeric";
128 static const struct String str_comparator_iascii_numeric={ str_comparator_iascii_numeric_c, 26 };
131 /*************************************************
132 * Encode to quoted-printable *
133 *************************************************/
140 static struct String *quoted_printable_encode(const struct String *src, struct String *dst)
143 const uschar *start,*end;
148 for (pass=0; pass<=1; ++pass)
155 dst->character=store_get(dst->length+1); /* plus one for \0 */
158 for (start=src->character,end=start+src->length; start<end; ++start)
175 || (ch>=62 && ch<=126)
181 && (*(start+1)!='\r' || *(start+2)!='\n')
196 else if (ch=='\r' && start+1<end && *(start+1)=='\n')
224 sprintf(CS new,"=%02X",ch);
231 *new='\0'; /* not included in length, but nice */
236 /*************************************************
237 * Octet-wise string comparison *
238 *************************************************/
242 needle UTF-8 string to search ...
243 haystack ... inside the haystack
244 match_prefix 1 to compare if needle is a prefix of haystack
246 Returns: 0 needle not found in haystack
250 static int eq_octet(const struct String *needle,
251 const struct String *haystack, int match_prefix)
259 h=haystack->character;
263 if (*n&0x80) return 0;
264 if (*h&0x80) return 0;
266 if (*n!=*h) return 0;
272 return (match_prefix ? nl==0 : nl==0 && hl==0);
276 /*************************************************
277 * ASCII case-insensitive string comparison *
278 *************************************************/
282 needle UTF-8 string to search ...
283 haystack ... inside the haystack
284 match_prefix 1 to compare if needle is a prefix of haystack
286 Returns: 0 needle not found in haystack
290 static int eq_asciicase(const struct String *needle,
291 const struct String *haystack, int match_prefix)
300 h=haystack->character;
306 if (nc&0x80) return 0;
307 if (hc&0x80) return 0;
309 /* tolower depends on the locale and only ASCII case must be insensitive */
310 if ((nc&0x80) || (hc&0x80)) { if (nc!=hc) return 0; }
311 else if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
317 return (match_prefix ? nl==0 : nl==0 && hl==0);
321 /*************************************************
322 * Octet-wise glob pattern search *
323 *************************************************/
327 needle pattern to search ...
328 haystack ... inside the haystack
330 Returns: 0 needle not found in haystack
334 static int eq_octetglob(const struct String *needle,
335 const struct String *haystack)
343 switch (n.character[0])
351 /* The greedy match is not yet well tested. Some day we may */
352 /* need to refer to the matched parts, so the code is already */
353 /* prepared for that. */
356 currentLength=h.length;
357 h.character+=h.length;
359 while (h.length<=currentLength)
361 if (eq_octetglob(&n,&h)) return 1;
362 else /* go back one octet */
373 if (eq_octetglob(&n,&h)) return 1;
374 else /* advance one octet */
407 (h.character[0]&0x80) || (n.character[0]&0x80) ||
409 h.character[0]!=n.character[0]
421 return (h.length==0);
425 /*************************************************
426 * ASCII case-insensitive glob pattern search *
427 *************************************************/
431 needle UTF-8 pattern to search ...
432 haystack ... inside the haystack
434 Returns: 0 needle not found in haystack
438 static int eq_asciicaseglob(const struct String *needle,
439 const struct String *haystack)
447 switch (n.character[0])
455 /* The greedy match is not yet well tested. Some day we may */
456 /* need to refer to the matched parts, so the code is already */
457 /* prepared for that. */
460 currentLength=h.length;
461 h.character+=h.length;
463 while (h.length<=currentLength)
465 if (eq_asciicaseglob(&n,&h)) return 1;
466 else /* go back one UTF-8 character */
468 if (h.length==currentLength) return 0;
471 if (h.character[0]&0x80)
473 while (h.length<currentLength && (*(h.character-1)&0x80))
485 if (eq_asciicaseglob(&n,&h)) return 1;
486 else /* advance one UTF-8 character */
488 if (h.character[0]&0x80)
490 while (h.length && (h.character[0]&0x80))
512 /* advance one UTF-8 character */
513 if (h.character[0]&0x80)
515 while (h.length && (h.character[0]&0x80))
540 if (h.length==0) return 0;
544 if ((hc&0x80) || (nc&0x80)) return 0;
546 /* tolower depends on the locale and only ASCII case must be insensitive */
547 if ((nc&0x80) || (hc&0x80)) { if (nc!=hc) return 0; }
548 else if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
556 return (h.length==0);
560 /*************************************************
561 * ASCII numeric comparison *
562 *************************************************/
566 a first numeric string
567 b second numeric string
568 relop relational operator
570 Returns: 0 not (a relop b)
574 static int eq_asciinumeric(const struct String *a,
575 const struct String *b, enum RelOp relop)
578 const uschar *as,*aend,*bs,*bend;
582 aend=a->character+a->length;
584 bend=b->character+b->length;
586 while (*as>='0' && *as<='9' && as<aend) ++as;
588 while (*bs>='0' && *bs<='9' && bs<bend) ++bs;
591 if (al && bl==0) cmp=-1;
592 else if (al==0 && bl==0) cmp=0;
593 else if (al==0 && bl) cmp=1;
597 if (cmp==0) cmp=memcmp(a->character,b->character,al);
601 case LT: return cmp<0;
602 case LE: return cmp<=0;
603 case EQ: return cmp==0;
604 case GE: return cmp>=0;
605 case GT: return cmp>0;
606 case NE: return cmp!=0;
613 /*************************************************
615 *************************************************/
619 needle UTF-8 pattern or string to search ...
620 haystack ... inside the haystack
624 Returns: 0 needle not found in haystack
626 -1 comparator does not offer matchtype
629 static int compare(struct Sieve *filter, const struct String *needle, const struct String *haystack,
630 enum Comparator co, enum MatchType mt)
634 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
635 (debug_selector & D_filter) != 0)
637 debug_printf("String comparison (match ");
640 case MATCH_IS: debug_printf(":is"); break;
641 case MATCH_CONTAINS: debug_printf(":contains"); break;
642 case MATCH_MATCHES: debug_printf(":matches"); break;
644 debug_printf(", comparison \"");
647 case COMP_OCTET: debug_printf("i;octet"); break;
648 case COMP_ASCII_CASEMAP: debug_printf("i;ascii-casemap"); break;
649 case COMP_ASCII_NUMERIC: debug_printf("i;ascii-numeric"); break;
651 debug_printf("\"):\n");
652 debug_printf(" Search = %s (%d chars)\n", needle->character,needle->length);
653 debug_printf(" Inside = %s (%d chars)\n", haystack->character,haystack->length);
663 if (eq_octet(needle,haystack,0)) r=1;
666 case COMP_ASCII_CASEMAP:
668 if (eq_asciicase(needle,haystack,0)) r=1;
671 case COMP_ASCII_NUMERIC:
673 if (!filter->require_iascii_numeric)
675 filter->errmsg=CUS "missing previous require \"comparator-i;ascii-numeric\";";
678 if (eq_asciinumeric(needle,haystack,EQ)) r=1;
692 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_octet(needle,&h,1)) { r=1; break; }
695 case COMP_ASCII_CASEMAP:
697 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_asciicase(needle,&h,1)) { r=1; break; }
702 filter->errmsg=CUS "comparator does not offer specified matchtype";
714 if (eq_octetglob(needle,haystack)) r=1;
717 case COMP_ASCII_CASEMAP:
719 if (eq_asciicaseglob(needle,haystack)) r=1;
724 filter->errmsg=CUS "comparator does not offer specified matchtype";
731 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
732 (debug_selector & D_filter) != 0)
733 debug_printf(" Result %s\n",r?"true":"false");
738 /*************************************************
739 * Check header field syntax *
740 *************************************************/
743 RFC 2822, section 3.6.8 says:
747 ftext = %d33-57 / ; Any character except
748 %d59-126 ; controls, SP, and
751 That forbids 8-bit header fields. This implementation accepts them, since
752 all of Exim is 8-bit clean, so it adds %d128-%d255.
755 header header field to quote for suitable use in Exim expansions
757 Returns: 0 string is not a valid header field
758 1 string is a value header field
761 static int is_header(const struct String *header)
771 if (((unsigned char)*h)<33 || ((unsigned char)*h)==':' || ((unsigned char)*h)==127) return 0;
782 /*************************************************
783 * Quote special characters string *
784 *************************************************/
788 header header field to quote for suitable use in Exim expansions
791 Returns: quoted string
794 static const uschar *quote(const struct String *header)
809 quoted=string_cat(quoted,&size,&ptr,CUS "\\0",2);
816 quoted=string_cat(quoted,&size,&ptr,CUS "\\",1);
820 quoted=string_cat(quoted,&size,&ptr,h,1);
826 quoted=string_cat(quoted,&size,&ptr,CUS "",1);
831 /*************************************************
832 * Add address to list of generated addresses *
833 *************************************************/
836 According to RFC 3028, duplicate delivery to the same address must
837 not happen, so the list is first searched for the address.
840 generated list of generated addresses
841 addr new address to add
842 file address denotes a file
847 static void add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
849 address_item *new_addr;
851 for (new_addr=*generated; new_addr; new_addr=new_addr->next)
853 if (Ustrcmp(new_addr->address,addr)==0 && (file ? testflag(new_addr, af_pfr|af_file) : 1))
855 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
857 debug_printf("Repeated %s `%s' ignored.\n",file ? "fileinto" : "redirect", addr);
863 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
865 debug_printf("%s `%s'\n",file ? "fileinto" : "redirect", addr);
867 new_addr=deliver_make_addr(addr,TRUE);
870 setflag(new_addr, af_pfr|af_file);
873 new_addr->p.errors_address = NULL;
874 new_addr->next = *generated;
875 *generated = new_addr;
879 /*************************************************
880 * Return decoded header field *
881 *************************************************/
885 value returned value of the field
886 header name of the header field
888 Returns: nothing The expanded string is empty
889 in case there is no such header
892 static void expand_header(struct String *value, const struct String *header)
898 value->character=(uschar*)0;
900 t=r=s=expand_string(string_sprintf("$rheader_%s",quote(header)));
907 while (*r==' ' || *r=='\t') ++r;
914 value->character=rfc2047_decode(s,TRUE,US"utf-8",'\0',&value->length,&errmsg);
918 /*************************************************
919 * Parse remaining hash comment *
920 *************************************************/
924 Comment up to terminating CRLF
927 filter points to the Sieve filter including its state
933 static int parse_hashcomment(struct Sieve *filter)
939 if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
941 if (*filter->pc=='\n')
954 filter->errmsg=CUS "missing end of comment";
959 /*************************************************
960 * Parse remaining C-style comment *
961 *************************************************/
965 Everything up to star slash
968 filter points to the Sieve filter including its state
974 static int parse_comment(struct Sieve *filter)
979 if (*filter->pc=='*' && *(filter->pc+1)=='/')
986 filter->errmsg=CUS "missing end of comment";
991 /*************************************************
992 * Parse optional white space *
993 *************************************************/
997 Spaces, tabs, CRLFs, hash comments or C-style comments
1000 filter points to the Sieve filter including its state
1006 static int parse_white(struct Sieve *filter)
1010 if (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
1012 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
1014 else if (*filter->pc=='\n')
1024 else if (*filter->pc=='#')
1026 if (parse_hashcomment(filter)==-1) return -1;
1028 else if (*filter->pc=='/' && *(filter->pc+1)=='*')
1030 if (parse_comment(filter)==-1) return -1;
1038 /*************************************************
1039 * Parse a optional string *
1040 *************************************************/
1044 quoted-string = DQUOTE *CHAR DQUOTE
1045 ;; in general, \ CHAR inside a string maps to CHAR
1046 ;; so \" maps to " and \\ maps to \
1047 ;; note that newlines and other characters are all allowed
1050 multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
1051 *(multi-line-literal / multi-line-dotstuff)
1053 multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
1054 multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
1055 ;; A line containing only "." ends the multi-line.
1056 ;; Remove a leading '.' if followed by another '.'.
1057 string = quoted-string / multi-line
1060 filter points to the Sieve filter including its state
1061 id specifies identifier to match
1065 0 identifier not matched
1068 static int parse_string(struct Sieve *filter, struct String *data)
1073 data->character=(uschar*)0;
1074 if (*filter->pc=='"') /* quoted string */
1079 if (*filter->pc=='"') /* end of string */
1081 int foo=data->length;
1084 data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
1087 else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
1089 if (*(filter->pc+1)=='0') data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
1090 else data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc+1,1);
1093 else /* regular character */
1095 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1099 filter->errmsg=CUS "missing end of string";
1102 else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
1105 /* skip optional white space followed by hashed comment or CRLF */
1106 while (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
1107 if (*filter->pc=='#')
1109 if (parse_hashcomment(filter)==-1) return -1;
1112 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
1114 else if (*filter->pc=='\n')
1126 filter->errmsg=CUS "syntax error";
1132 if (*filter->pc=='\r' && *(filter->pc+1)=='\n') /* end of line */
1134 if (*filter->pc=='\n') /* end of line */
1137 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
1145 if (*filter->pc=='.' && *(filter->pc+1)=='\r' && *(filter->pc+2)=='\n') /* end of string */
1147 if (*filter->pc=='.' && *(filter->pc+1)=='\n') /* end of string */
1150 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
1159 else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
1161 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS ".",1);
1165 else /* regular character */
1167 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1171 filter->errmsg=CUS "missing end of multi line string";
1178 /*************************************************
1179 * Parse a specific identifier *
1180 *************************************************/
1184 identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
1187 filter points to the Sieve filter including its state
1188 id specifies identifier to match
1191 0 identifier not matched
1194 static int parse_identifier(struct Sieve *filter, const uschar *id)
1196 size_t idlen=Ustrlen(id);
1198 if (Ustrncmp(filter->pc,id,idlen)==0)
1200 uschar next=filter->pc[idlen];
1202 if ((next>='A' && next<='Z') || (next>='a' && next<='z') || next=='_' || (next>='0' && next<='9')) return 0;
1210 /*************************************************
1212 *************************************************/
1216 number = 1*DIGIT [QUANTIFIER]
1217 QUANTIFIER = "K" / "M" / "G"
1220 filter points to the Sieve filter including its state
1224 -1 no string list found
1227 static int parse_number(struct Sieve *filter, unsigned long *data)
1231 if (*filter->pc>='0' && *filter->pc<='9')
1236 d=Ustrtoul(filter->pc,&e,10);
1239 filter->errmsg=CUstrerror(ERANGE);
1244 if (*filter->pc=='K') { u=1024; ++filter->pc; }
1245 else if (*filter->pc=='M') { u=1024*1024; ++filter->pc; }
1246 else if (*filter->pc=='G') { u=1024*1024*1024; ++filter->pc; }
1247 if (d>(ULONG_MAX/u))
1249 filter->errmsg=CUstrerror(ERANGE);
1258 filter->errmsg=CUS "missing number";
1264 /*************************************************
1265 * Parse a string list *
1266 *************************************************/
1270 string-list = "[" string *("," string) "]" / string
1273 filter points to the Sieve filter including its state
1274 data returns string list
1277 -1 no string list found
1280 static int parse_stringlist(struct Sieve *filter, struct String **data)
1282 const uschar *orig=filter->pc;
1285 struct String *d=(struct String*)0;
1288 if (*filter->pc=='[') /* string list */
1293 if (parse_white(filter)==-1) goto error;
1294 if ((dataLength+1)>=dataCapacity) /* increase buffer */
1297 int newCapacity; /* Don't amalgamate with next line; some compilers grumble */
1298 newCapacity=dataCapacity?(dataCapacity*=2):(dataCapacity=4);
1299 if ((new=(struct String*)store_get(sizeof(struct String)*newCapacity))==(struct String*)0)
1301 filter->errmsg=CUstrerror(errno);
1304 if (d) memcpy(new,d,sizeof(struct String)*dataLength);
1306 dataCapacity=newCapacity;
1308 m=parse_string(filter,&d[dataLength]);
1311 if (dataLength==0) break;
1314 filter->errmsg=CUS "missing string";
1318 else if (m==-1) goto error;
1320 if (parse_white(filter)==-1) goto error;
1321 if (*filter->pc==',') ++filter->pc;
1324 if (*filter->pc==']')
1326 d[dataLength].character=(uschar*)0;
1327 d[dataLength].length=-1;
1334 filter->errmsg=CUS "missing closing bracket";
1338 else /* single string */
1340 if ((d=store_get(sizeof(struct String)*2))==(struct String*)0)
1344 m=parse_string(filter,&d[0]);
1356 d[1].character=(uschar*)0;
1363 filter->errmsg=CUS "missing string list";
1368 /*************************************************
1369 * Parse an optional address part specifier *
1370 *************************************************/
1374 address-part = ":localpart" / ":domain" / ":all"
1375 address-part =/ ":user" / ":detail"
1378 filter points to the Sieve filter including its state
1379 a returns address part specified
1382 0 no comparator found
1386 static int parse_addresspart(struct Sieve *filter, enum AddressPart *a)
1389 if (parse_identifier(filter,CUS ":user")==1)
1391 if (!filter->require_subaddress)
1393 filter->errmsg=CUS "missing previous require \"subaddress\";";
1399 else if (parse_identifier(filter,CUS ":detail")==1)
1401 if (!filter->require_subaddress)
1403 filter->errmsg=CUS "missing previous require \"subaddress\";";
1411 if (parse_identifier(filter,CUS ":localpart")==1)
1413 *a=ADDRPART_LOCALPART;
1416 else if (parse_identifier(filter,CUS ":domain")==1)
1421 else if (parse_identifier(filter,CUS ":all")==1)
1430 /*************************************************
1431 * Parse an optional comparator *
1432 *************************************************/
1436 comparator = ":comparator" <comparator-name: string>
1439 filter points to the Sieve filter including its state
1440 c returns comparator
1443 0 no comparator found
1444 -1 incomplete comparator found
1447 static int parse_comparator(struct Sieve *filter, enum Comparator *c)
1449 struct String comparator_name;
1451 if (parse_identifier(filter,CUS ":comparator")==0) return 0;
1452 if (parse_white(filter)==-1) return -1;
1453 switch (parse_string(filter,&comparator_name))
1458 filter->errmsg=CUS "missing comparator";
1465 if (eq_asciicase(&comparator_name,&str_ioctet,0))
1470 else if (eq_asciicase(&comparator_name,&str_iascii_casemap,0))
1472 *c=COMP_ASCII_CASEMAP;
1475 else if (eq_asciicase(&comparator_name,&str_iascii_numeric,0))
1477 *c=COMP_ASCII_NUMERIC;
1482 filter->errmsg=CUS "invalid comparator";
1491 /*************************************************
1492 * Parse an optional match type *
1493 *************************************************/
1497 match-type = ":is" / ":contains" / ":matches"
1500 filter points to the Sieve filter including its state
1501 m returns match type
1504 0 no match type found
1507 static int parse_matchtype(struct Sieve *filter, enum MatchType *m)
1509 if (parse_identifier(filter,CUS ":is")==1)
1514 else if (parse_identifier(filter,CUS ":contains")==1)
1519 else if (parse_identifier(filter,CUS ":matches")==1)
1528 /*************************************************
1529 * Parse and interpret an optional test list *
1530 *************************************************/
1534 test-list = "(" test *("," test) ")"
1537 filter points to the Sieve filter including its state
1538 n total number of tests
1539 true number of passed tests
1540 exec Execute parsed statements
1543 0 no test list found
1544 -1 syntax or execution error
1547 static int parse_testlist(struct Sieve *filter, int *n, int *true, int exec)
1549 if (parse_white(filter)==-1) return -1;
1550 if (*filter->pc=='(')
1559 switch (parse_test(filter,&cond,exec))
1562 case 0: filter->errmsg=CUS "missing test"; return -1;
1563 default: ++*n; if (cond) ++*true; break;
1565 if (parse_white(filter)==-1) return -1;
1566 if (*filter->pc==',') ++filter->pc;
1569 if (*filter->pc==')')
1576 filter->errmsg=CUS "missing closing paren";
1584 /*************************************************
1585 * Parse and interpret an optional test *
1586 *************************************************/
1590 filter points to the Sieve filter including its state
1591 cond returned condition status
1592 exec Execute parsed statements
1596 -1 syntax or execution error
1599 static int parse_test(struct Sieve *filter, int *cond, int exec)
1601 if (parse_white(filter)==-1) return -1;
1602 if (parse_identifier(filter,CUS "address"))
1605 address-test = "address" { [address-part] [comparator] [match-type] }
1606 <header-list: string-list> <key-list: string-list>
1608 header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
1611 enum AddressPart addressPart=ADDRPART_ALL;
1612 enum Comparator comparator=COMP_ASCII_CASEMAP;
1613 enum MatchType matchType=MATCH_IS;
1614 struct String *hdr,*h,*key,*k;
1620 if (parse_white(filter)==-1) return -1;
1621 if ((m=parse_addresspart(filter,&addressPart))!=0)
1623 if (m==-1) return -1;
1626 filter->errmsg=CUS "address part already specified";
1631 else if ((m=parse_comparator(filter,&comparator))!=0)
1633 if (m==-1) return -1;
1636 filter->errmsg=CUS "comparator already specified";
1641 else if ((m=parse_matchtype(filter,&matchType))!=0)
1643 if (m==-1) return -1;
1646 filter->errmsg=CUS "match type already specified";
1653 if (parse_white(filter)==-1) return -1;
1654 if ((m=parse_stringlist(filter,&hdr))!=1)
1656 if (m==0) filter->errmsg=CUS "header string list expected";
1659 if (parse_white(filter)==-1) return -1;
1660 if ((m=parse_stringlist(filter,&key))!=1)
1662 if (m==0) filter->errmsg=CUS "key string list expected";
1666 for (h=hdr; h->length!=-1 && !*cond; ++h)
1668 uschar *header_value=(uschar*)0,*extracted_addr,*end_addr;
1672 !eq_asciicase(h,&str_from,0)
1673 && !eq_asciicase(h,&str_to,0)
1674 && !eq_asciicase(h,&str_cc,0)
1675 && !eq_asciicase(h,&str_bcc,0)
1676 && !eq_asciicase(h,&str_sender,0)
1677 && !eq_asciicase(h,&str_resent_from,0)
1678 && !eq_asciicase(h,&str_resent_to,0)
1681 filter->errmsg=CUS "invalid header field";
1686 /* We are only interested in addresses below, so no MIME decoding */
1687 header_value=expand_string(string_sprintf("$rheader_%s",quote(h)));
1688 if (header_value == NULL)
1690 filter->errmsg=CUS "header string expansion failed";
1693 parse_allow_group = TRUE;
1694 while (*header_value && !*cond)
1697 int start, end, domain;
1701 end_addr = parse_find_address_end(header_value, FALSE);
1702 saveend = *end_addr;
1704 extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
1706 if (extracted_addr) switch (addressPart)
1708 case ADDRPART_ALL: part=extracted_addr; break;
1712 case ADDRPART_LOCALPART: part=extracted_addr; part[domain-1]='\0'; break;
1713 case ADDRPART_DOMAIN: part=extracted_addr+domain; break;
1715 case ADDRPART_DETAIL:
1721 *end_addr = saveend;
1724 for (k=key; k->length!=-1; ++k)
1726 struct String partStr;
1728 partStr.character=part;
1729 partStr.length=Ustrlen(part);
1732 *cond=compare(filter,k,&partStr,comparator,matchType);
1733 if (*cond==-1) return -1;
1738 if (saveend == 0) break;
1739 header_value = end_addr + 1;
1745 else if (parse_identifier(filter,CUS "allof"))
1748 allof-test = "allof" <tests: test-list>
1753 switch (parse_testlist(filter,&n,&true,exec))
1756 case 0: filter->errmsg=CUS "missing test list"; return -1;
1757 default: *cond=(n==true); return 1;
1760 else if (parse_identifier(filter,CUS "anyof"))
1763 anyof-test = "anyof" <tests: test-list>
1768 switch (parse_testlist(filter,&n,&true,exec))
1771 case 0: filter->errmsg=CUS "missing test list"; return -1;
1772 default: *cond=(true>0); return 1;
1775 else if (parse_identifier(filter,CUS "exists"))
1778 exists-test = "exists" <header-names: string-list>
1781 struct String *hdr,*h;
1784 if (parse_white(filter)==-1) return -1;
1785 if ((m=parse_stringlist(filter,&hdr))!=1)
1787 if (m==0) filter->errmsg=CUS "header string list expected";
1793 for (h=hdr; h->length!=-1 && *cond; ++h)
1797 header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
1798 if (header_def == NULL)
1800 filter->errmsg=CUS "header string expansion failed";
1803 if (Ustrcmp(header_def,"false")==0) *cond=0;
1808 else if (parse_identifier(filter,CUS "false"))
1811 false-test = "false"
1817 else if (parse_identifier(filter,CUS "header"))
1820 header-test = "header" { [comparator] [match-type] }
1821 <header-names: string-list> <key-list: string-list>
1824 enum Comparator comparator=COMP_ASCII_CASEMAP;
1825 enum MatchType matchType=MATCH_IS;
1826 struct String *hdr,*h,*key,*k;
1832 if (parse_white(filter)==-1) return -1;
1833 if ((m=parse_comparator(filter,&comparator))!=0)
1835 if (m==-1) return -1;
1838 filter->errmsg=CUS "comparator already specified";
1843 else if ((m=parse_matchtype(filter,&matchType))!=0)
1845 if (m==-1) return -1;
1848 filter->errmsg=CUS "match type already specified";
1855 if (parse_white(filter)==-1) return -1;
1856 if ((m=parse_stringlist(filter,&hdr))!=1)
1858 if (m==0) filter->errmsg=CUS "header string list expected";
1861 if (parse_white(filter)==-1) return -1;
1862 if ((m=parse_stringlist(filter,&key))!=1)
1864 if (m==0) filter->errmsg=CUS "key string list expected";
1868 for (h=hdr; h->length!=-1 && !*cond; ++h)
1872 filter->errmsg=CUS "invalid header field";
1877 struct String header_value;
1880 expand_header(&header_value,h);
1881 header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
1882 if (header_value.character == NULL || header_def == NULL)
1884 filter->errmsg=CUS "header string expansion failed";
1887 for (k=key; k->length!=-1; ++k)
1889 if (Ustrcmp(header_def,"true")==0)
1891 *cond=compare(filter,k,&header_value,comparator,matchType);
1892 if (*cond==-1) return -1;
1900 else if (parse_identifier(filter,CUS "not"))
1902 if (parse_white(filter)==-1) return -1;
1903 switch (parse_test(filter,cond,exec))
1906 case 0: filter->errmsg=CUS "missing test"; return -1;
1907 default: *cond=!*cond; return 1;
1910 else if (parse_identifier(filter,CUS "size"))
1913 relop = ":over" / ":under"
1914 size-test = "size" relop <limit: number>
1917 unsigned long limit;
1920 if (parse_white(filter)==-1) return -1;
1921 if (parse_identifier(filter,CUS ":over")) overNotUnder=1;
1922 else if (parse_identifier(filter,CUS ":under")) overNotUnder=0;
1925 filter->errmsg=CUS "missing :over or :under";
1928 if (parse_white(filter)==-1) return -1;
1929 if (parse_number(filter,&limit)==-1) return -1;
1930 *cond=(overNotUnder ? (message_size>limit) : (message_size<limit));
1933 else if (parse_identifier(filter,CUS "true"))
1938 else if (parse_identifier(filter,CUS "envelope"))
1941 envelope-test = "envelope" { [comparator] [address-part] [match-type] }
1942 <envelope-part: string-list> <key-list: string-list>
1944 envelope-part is case insensitive "from" or "to"
1947 enum Comparator comparator=COMP_ASCII_CASEMAP;
1948 enum AddressPart addressPart=ADDRPART_ALL;
1949 enum MatchType matchType=MATCH_IS;
1950 struct String *env,*e,*key,*k;
1954 if (!filter->require_envelope)
1956 filter->errmsg=CUS "missing previous require \"envelope\";";
1961 if (parse_white(filter)==-1) return -1;
1962 if ((m=parse_comparator(filter,&comparator))!=0)
1964 if (m==-1) return -1;
1967 filter->errmsg=CUS "comparator already specified";
1972 else if ((m=parse_addresspart(filter,&addressPart))!=0)
1974 if (m==-1) return -1;
1977 filter->errmsg=CUS "address part already specified";
1982 else if ((m=parse_matchtype(filter,&matchType))!=0)
1984 if (m==-1) return -1;
1987 filter->errmsg=CUS "match type already specified";
1994 if (parse_white(filter)==-1) return -1;
1995 if ((m=parse_stringlist(filter,&env))!=1)
1997 if (m==0) filter->errmsg=CUS "envelope string list expected";
2000 if (parse_white(filter)==-1) return -1;
2001 if ((m=parse_stringlist(filter,&key))!=1)
2003 if (m==0) filter->errmsg=CUS "key string list expected";
2007 for (e=env; e->character; ++e)
2009 const uschar *envelopeExpr=CUS 0;
2010 uschar *envelope=US 0;
2012 if (eq_asciicase(e,&str_from,0))
2014 switch (addressPart)
2016 case ADDRPART_ALL: envelopeExpr=CUS "$sender_address"; break;
2020 case ADDRPART_LOCALPART: envelopeExpr=CUS "${local_part:$sender_address}"; break;
2021 case ADDRPART_DOMAIN: envelopeExpr=CUS "${domain:$sender_address}"; break;
2023 case ADDRPART_DETAIL:
2029 else if (eq_asciicase(e,&str_to,0))
2031 switch (addressPart)
2033 case ADDRPART_ALL: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
2035 case ADDRPART_USER: envelopeExpr=CUS "$local_part_prefix$local_part"; break;
2036 case ADDRPART_DETAIL: envelopeExpr=CUS "$local_part_suffix"; break;
2038 case ADDRPART_LOCALPART: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix"; break;
2039 case ADDRPART_DOMAIN: envelopeExpr=CUS "$domain"; break;
2044 filter->errmsg=CUS "invalid envelope string";
2047 if (exec && envelopeExpr)
2049 if ((envelope=expand_string(US envelopeExpr)) == NULL)
2051 filter->errmsg=CUS "header string expansion failed";
2054 for (k=key; k->length!=-1; ++k)
2056 struct String envelopeStr;
2058 envelopeStr.character=envelope;
2059 envelopeStr.length=Ustrlen(envelope);
2060 *cond=compare(filter,&envelopeStr,k,comparator,matchType);
2061 if (*cond==-1) return -1;
2072 /*************************************************
2073 * Parse and interpret an optional block *
2074 *************************************************/
2078 filter points to the Sieve filter including its state
2079 exec Execute parsed statements
2080 generated where to hang newly-generated addresses
2082 Returns: 2 success by stop
2084 0 no block command found
2085 -1 syntax or execution error
2088 static int parse_block(struct Sieve *filter, int exec,
2089 address_item **generated)
2093 if (parse_white(filter)==-1) return -1;
2094 if (*filter->pc=='{')
2097 if ((r=parse_commands(filter,exec,generated))==-1 || r==2) return r;
2098 if (*filter->pc=='}')
2105 filter->errmsg=CUS "missing closing brace";
2113 /*************************************************
2114 * Match a semicolon *
2115 *************************************************/
2119 filter points to the Sieve filter including its state
2125 static int parse_semicolon(struct Sieve *filter)
2127 if (parse_white(filter)==-1) return -1;
2128 if (*filter->pc==';')
2135 filter->errmsg=CUS "missing semicolon";
2141 /*************************************************
2142 * Parse and interpret a Sieve command *
2143 *************************************************/
2147 filter points to the Sieve filter including its state
2148 exec Execute parsed statements
2149 generated where to hang newly-generated addresses
2151 Returns: 2 success by stop
2153 -1 syntax or execution error
2155 static int parse_commands(struct Sieve *filter, int exec,
2156 address_item **generated)
2160 if (parse_white(filter)==-1) return -1;
2161 if (parse_identifier(filter,CUS "if"))
2164 if-command = "if" test block *( "elsif" test block ) [ else block ]
2167 int cond,m,unsuccessful;
2170 if (parse_white(filter)==-1) return -1;
2171 if ((m=parse_test(filter,&cond,exec))==-1) return -1;
2174 filter->errmsg=CUS "missing test";
2177 m=parse_block(filter,exec ? cond : 0, generated);
2178 if (m==-1 || m==2) return m;
2181 filter->errmsg=CUS "missing block";
2184 unsuccessful = !cond;
2185 for (;;) /* elsif test block */
2187 if (parse_white(filter)==-1) return -1;
2188 if (parse_identifier(filter,CUS "elsif"))
2190 if (parse_white(filter)==-1) return -1;
2191 m=parse_test(filter,&cond,exec && unsuccessful);
2192 if (m==-1 || m==2) return m;
2195 filter->errmsg=CUS "missing test";
2198 m=parse_block(filter,exec && unsuccessful ? cond : 0, generated);
2199 if (m==-1 || m==2) return m;
2202 filter->errmsg=CUS "missing block";
2205 if (exec && unsuccessful && cond) unsuccessful = 0;
2210 if (parse_white(filter)==-1) return -1;
2211 if (parse_identifier(filter,CUS "else"))
2213 m=parse_block(filter,exec && unsuccessful, generated);
2214 if (m==-1 || m==2) return m;
2217 filter->errmsg=CUS "missing block";
2222 else if (parse_identifier(filter,CUS "stop"))
2225 stop-command = "stop" { stop-options } ";"
2229 if (parse_semicolon(filter)==-1) return -1;
2232 filter->pc+=Ustrlen(filter->pc);
2236 else if (parse_identifier(filter,CUS "keep"))
2239 keep-command = "keep" { keep-options } ";"
2243 if (parse_semicolon(filter)==-1) return -1;
2246 add_addr(generated,US"inbox",1,0,0,0);
2250 else if (parse_identifier(filter,CUS "discard"))
2253 discard-command = "discard" { discard-options } ";"
2257 if (parse_semicolon(filter)==-1) return -1;
2258 if (exec) filter->keep=0;
2260 else if (parse_identifier(filter,CUS "redirect"))
2263 redirect-command = "redirect" redirect-options "string" ";"
2265 redirect-options =) ":copy"
2268 struct String recipient;
2274 if (parse_white(filter)==-1) return -1;
2275 if (parse_identifier(filter,CUS ":copy")==1)
2277 if (!filter->require_copy)
2279 filter->errmsg=CUS "missing previous require \"copy\";";
2286 if (parse_white(filter)==-1) return -1;
2287 if ((m=parse_string(filter,&recipient))!=1)
2289 if (m==0) filter->errmsg=CUS "missing redirect recipient string";
2292 if (strchr(CCS recipient.character,'@')==(char*)0)
2294 filter->errmsg=CUS "unqualified recipient address";
2299 add_addr(generated,recipient.character,0,0,0,0);
2300 if (!copy) filter->keep = 0;
2302 if (parse_semicolon(filter)==-1) return -1;
2304 else if (parse_identifier(filter,CUS "fileinto"))
2307 fileinto-command = "fileinto" { fileinto-options } string ";"
2309 fileinto-options =) [ ":copy" ]
2312 struct String folder;
2315 unsigned long maxage, maxmessages, maxstorage;
2318 maxage = maxmessages = maxstorage = 0;
2319 if (!filter->require_fileinto)
2321 filter->errmsg=CUS "missing previous require \"fileinto\";";
2326 if (parse_white(filter)==-1) return -1;
2327 if (parse_identifier(filter,CUS ":copy")==1)
2329 if (!filter->require_copy)
2331 filter->errmsg=CUS "missing previous require \"copy\";";
2338 if (parse_white(filter)==-1) return -1;
2339 if ((m=parse_string(filter,&folder))!=1)
2341 if (m==0) filter->errmsg=CUS "missing fileinto folder string";
2344 m=0; s=folder.character;
2345 if (folder.length==0) m=1;
2346 if (Ustrcmp(s,"..")==0 || Ustrncmp(s,"../",3)==0) m=1;
2349 if (Ustrcmp(s,"/..")==0 || Ustrncmp(s,"/../",4)==0) { m=1; break; }
2354 filter->errmsg=CUS "invalid folder";
2359 add_addr(generated, folder.character, 1, maxage, maxmessages, maxstorage);
2360 if (!copy) filter->keep = 0;
2362 if (parse_semicolon(filter)==-1) return -1;
2365 else if (parse_identifier(filter,CUS "vacation"))
2368 vacation-command = "vacation" { vacation-options } <reason: string> ";"
2369 vacation-options = [":days" number]
2370 [":addresses" string-list]
2377 struct String *addresses=(struct String*)0;
2378 struct String subject;
2380 string_item *aliases;
2381 struct String reason;
2383 if (!filter->require_vacation)
2385 filter->errmsg=CUS "missing previous require \"vacation\";";
2390 if (filter->vacation_ran)
2392 filter->errmsg=CUS "trying to execute vacation more than once";
2395 filter->vacation_ran=1;
2397 days=VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
2398 subject.character=(uschar*)0;
2404 if (parse_white(filter)==-1) return -1;
2405 if (parse_identifier(filter,CUS ":days")==1)
2407 if (parse_white(filter)==-1) return -1;
2408 if (parse_number(filter,&days)==-1) return -1;
2409 if (days<VACATION_MIN_DAYS) days=VACATION_MIN_DAYS;
2410 else if (days>VACATION_MAX_DAYS) days=VACATION_MAX_DAYS;
2412 else if (parse_identifier(filter,CUS ":addresses")==1)
2416 if (parse_white(filter)==-1) return -1;
2417 if ((m=parse_stringlist(filter,&addresses))!=1)
2419 if (m==0) filter->errmsg=CUS "addresses string list expected";
2422 for (a=addresses; a->length!=-1; ++a)
2426 new=store_get(sizeof(string_item));
2427 new->text=store_get(a->length+1);
2428 if (a->length) memcpy(new->text,a->character,a->length);
2429 new->text[a->length]='\0';
2434 else if (parse_identifier(filter,CUS ":subject")==1)
2436 if (parse_white(filter)==-1) return -1;
2437 if ((m=parse_string(filter,&subject))!=1)
2439 if (m==0) filter->errmsg=CUS "subject string expected";
2443 else if (parse_identifier(filter,CUS ":mime")==1)
2447 if (parse_white(filter)==-1) return -1;
2448 if ((m=parse_string(filter,&reason))!=1)
2450 if (m==0) filter->errmsg=CUS "missing reason string";
2453 if (parse_semicolon(filter)==-1) return -1;
2460 int buffer_capacity;
2464 uschar hexdigest[33];
2468 if (filter_personal(aliases,TRUE))
2471 /* ensure oncelog directory exists; failure will be detected later */
2473 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
2475 /* build oncelog filename */
2477 key.character=(uschar*)0;
2480 if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
2481 key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
2482 key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
2484 md5_end(&base, key.character, key.length, digest);
2485 for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
2486 capacity=Ustrlen(filter->vacation_directory);
2488 once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
2489 once=string_cat(once,&capacity,&start,hexdigest,33);
2491 /* process subject */
2493 if (subject.length==-1)
2495 expand_header(&subject,&str_subject);
2496 while (subject.length>=4 && Ustrncmp(subject.character,"Re: ",4)==0)
2498 subject.character+=4;
2503 subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
2504 subject.length=start;
2507 /* add address to list of generated addresses */
2509 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
2510 setflag(addr, af_pfr);
2511 setflag(addr, af_ignore_error);
2512 addr->next = *generated;
2514 addr->reply = store_get(sizeof(reply_item));
2515 memset(addr->reply,0,sizeof(reply_item)); /* XXX */
2516 addr->reply->to = string_copy(sender_address);
2517 /* Allocation is larger than neccessary, but enough even for split MIME words */
2518 buffer_capacity=16+4*subject.length;
2519 buffer=store_get(buffer_capacity);
2520 addr->reply->subject=parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity);
2521 addr->reply->oncelog=once;
2522 addr->reply->once_repeat=days*86400;
2524 /* build body and MIME headers */
2528 uschar *mime_body,*reason_end;
2530 static const uschar nlnl[]="\r\n\r\n";
2532 static const uschar nlnl[]="\n\n";
2537 mime_body=reason.character,reason_end=reason.character+reason.length;
2538 mime_body<(reason_end-sizeof(nlnl)-1) && memcmp(mime_body,nlnl,sizeof(nlnl)-1);
2543 addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
2546 if (mime_body<reason_end) mime_body+=sizeof(nlnl)-1;
2547 addr->reply->text = string_cat(NULL,&capacity,&start,mime_body,reason_end-mime_body);
2554 start = reason.length;
2555 addr->reply->headers = US"MIME-Version: 1.0\n"
2556 "Content-Type: text/plain;\n"
2557 "\tcharset=\"utf-8\"\n"
2558 "Content-Transfer-Encoding: quoted-printable";
2559 addr->reply->text = quoted_printable_encode(&reason,&qp)->character;
2571 /*************************************************
2572 * Parse and interpret a sieve filter *
2573 *************************************************/
2577 filter points to the Sieve filter including its state
2578 exec Execute parsed statements
2579 generated where to hang newly-generated addresses
2582 -1 syntax or execution error
2585 static int parse_start(struct Sieve *filter, int exec,
2586 address_item **generated)
2588 filter->pc=filter->filter;
2591 filter->require_envelope=0;
2592 filter->require_fileinto=0;
2594 filter->require_subaddress=0;
2597 filter->require_vacation=0;
2598 filter->vacation_ran=0;
2600 filter->require_copy=0;
2601 filter->require_iascii_numeric=0;
2603 if (parse_white(filter)==-1) return -1;
2605 if (exec && filter->vacation_directory != NULL) /* 2nd test added by PH */
2608 struct dirent *oncelog;
2609 struct stat properties;
2612 /* clean up old vacation log databases */
2614 oncelogdir=opendir(CS filter->vacation_directory);
2616 if (oncelogdir ==(DIR*)0 && errno != ENOENT)
2618 filter->errmsg=CUS "unable to open vacation directory";
2622 if (oncelogdir != NULL)
2626 while ((oncelog=readdir(oncelogdir))!=(struct dirent*)0)
2628 if (strlen(oncelog->d_name)==32)
2630 uschar *s=string_sprintf("%s/%s",filter->vacation_directory,oncelog->d_name);
2631 if (Ustat(s,&properties)==0 && (properties.st_mtime+VACATION_MAX_DAYS*86400)<now)
2635 closedir(oncelogdir);
2639 while (parse_identifier(filter,CUS "require"))
2642 require-command = "require" <capabilities: string-list>
2645 struct String *cap,*check;
2648 if (parse_white(filter)==-1) return -1;
2649 if ((m=parse_stringlist(filter,&cap))!=1)
2651 if (m==0) filter->errmsg=CUS "capability string list expected";
2654 for (check=cap; check->character; ++check)
2656 if (eq_asciicase(check,&str_envelope,0)) filter->require_envelope=1;
2657 else if (eq_asciicase(check,&str_fileinto,0)) filter->require_fileinto=1;
2659 else if (eq_asciicase(check,&str_subaddress,0)) filter->require_subaddress=1;
2662 else if (eq_asciicase(check,&str_vacation,0))
2664 if (filter->vacation_directory == NULL)
2666 filter->errmsg=CUS "vacation disabled";
2669 filter->require_vacation=1;
2672 else if (eq_asciicase(check,&str_copy,0)) filter->require_copy=1;
2673 else if (eq_asciicase(check,&str_comparator_ioctet,0)) ;
2674 else if (eq_asciicase(check,&str_comparator_iascii_casemap,0)) ;
2675 else if (eq_asciicase(check,&str_comparator_iascii_numeric,0)) filter->require_iascii_numeric=1;
2678 filter->errmsg=CUS "unknown capability";
2682 if (parse_semicolon(filter)==-1) return -1;
2684 if (parse_commands(filter,exec,generated)==-1) return -1;
2687 filter->errmsg=CUS "syntax error";
2694 /*************************************************
2695 * Interpret a sieve filter file *
2696 *************************************************/
2700 filter points to the entire file, read into store as a single string
2701 options controls whether various special things are allowed, and requests
2702 special actions (not currently used)
2703 sieve_vacation_directory where to store vacation "once" files
2704 generated where to hang newly-generated addresses
2705 error where to pass back an error text
2707 Returns: FF_DELIVERED success, a significant action was taken
2708 FF_NOTDELIVERED success, no significant action
2709 FF_DEFER defer requested
2710 FF_FAIL fail requested
2711 FF_FREEZE freeze requested
2712 FF_ERROR there was a problem
2716 sieve_interpret(uschar *filter, int options, uschar *vacation_directory,
2717 address_item **generated, uschar **error)
2723 options = options; /* Keep picky compilers happy */
2726 DEBUG(D_route) debug_printf("Sieve: start of processing\n");
2727 sieve.filter=filter;
2729 if (vacation_directory == NULL)
2730 sieve.vacation_directory = NULL;
2733 sieve.vacation_directory=expand_string(vacation_directory);
2734 if (sieve.vacation_directory == NULL)
2736 *error = string_sprintf("failed to expand \"%s\" "
2737 "(sieve_vacation_directory): %s", vacation_directory,
2738 expand_string_message);
2743 #ifdef COMPILE_SYNTAX_CHECKER
2744 if (parse_start(&sieve,0,generated)==1)
2746 if (parse_start(&sieve,1,generated)==1)
2751 add_addr(generated,US"inbox",1,0,0,0);
2752 msg = string_sprintf("Keep");
2757 msg = string_sprintf("No keep");
2763 msg = string_sprintf("Sieve error: %s in line %d",sieve.errmsg,sieve.line);
2764 #ifdef COMPILE_SYNTAX_CHECKER
2768 add_addr(generated,US"inbox",1,0,0,0);
2773 #ifndef COMPILE_SYNTAX_CHECKER
2774 if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
2775 else debug_printf("%s\n", msg);
2778 DEBUG(D_route) debug_printf("Sieve: end of processing\n");