1 /* $Cambridge: exim/src/src/sieve.c,v 1.5 2005/02/15 15:48:46 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 the vacation Sieve extension. */
39 #define VACATION_MIN_DAYS 1
40 /* Must be >= VACATION_MIN_DAYS, must be > 7, should be > 30 */
41 #define VACATION_MAX_DAYS 31
43 /* Keep this at 75 to accept only RFC compliant MIME words. */
44 /* Increase it if you want to match headers from buggy MUAs. */
45 #define MIMEWORD_LENGTH 75
57 int require_subaddress;
63 uschar *vacation_directory;
65 int require_iascii_numeric;
68 enum Comparator { COMP_OCTET, COMP_ASCII_CASEMAP, COMP_ASCII_NUMERIC };
69 enum MatchType { MATCH_IS, MATCH_CONTAINS, MATCH_MATCHES };
71 enum AddressPart { ADDRPART_USER, ADDRPART_DETAIL, ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
73 enum AddressPart { ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
75 enum RelOp { LT, LE, EQ, GE, GT, NE };
83 static int parse_test(struct Sieve *filter, int *cond, int exec);
84 static int parse_commands(struct Sieve *filter, int exec, address_item **generated);
86 static uschar str_from_c[]="From";
87 static const struct String str_from={ str_from_c, 4 };
88 static uschar str_to_c[]="To";
89 static const struct String str_to={ str_to_c, 2 };
90 static uschar str_cc_c[]="Cc";
91 static const struct String str_cc={ str_cc_c, 2 };
92 static uschar str_bcc_c[]="Bcc";
93 static const struct String str_bcc={ str_bcc_c, 3 };
94 static uschar str_sender_c[]="Sender";
95 static const struct String str_sender={ str_sender_c, 6 };
96 static uschar str_resent_from_c[]="Resent-From";
97 static const struct String str_resent_from={ str_resent_from_c, 11 };
98 static uschar str_resent_to_c[]="Resent-To";
99 static const struct String str_resent_to={ str_resent_to_c, 9 };
100 static uschar str_fileinto_c[]="fileinto";
101 static const struct String str_fileinto={ str_fileinto_c, 8 };
102 static uschar str_envelope_c[]="envelope";
103 static const struct String str_envelope={ str_envelope_c, 8 };
105 static uschar str_subaddress_c[]="subaddress";
106 static const struct String str_subaddress={ str_subaddress_c, 10 };
109 static uschar str_vacation_c[]="vacation";
110 static const struct String str_vacation={ str_vacation_c, 8 };
111 static uschar str_subject_c[]="Subject";
112 static const struct String str_subject={ str_subject_c, 7 };
114 static uschar str_copy_c[]="copy";
115 static const struct String str_copy={ str_copy_c, 4 };
116 static uschar str_iascii_casemap_c[]="i;ascii-casemap";
117 static const struct String str_iascii_casemap={ str_iascii_casemap_c, 15 };
118 static uschar str_ioctet_c[]="i;octet";
119 static const struct String str_ioctet={ str_ioctet_c, 7 };
120 static uschar str_iascii_numeric_c[]="i;ascii-numeric";
121 static const struct String str_iascii_numeric={ str_iascii_numeric_c, 15 };
122 static uschar str_comparator_iascii_casemap_c[]="comparator-i;ascii-casemap";
123 static const struct String str_comparator_iascii_casemap={ str_comparator_iascii_casemap_c, 26 };
124 static uschar str_comparator_ioctet_c[]="comparator-i;octet";
125 static const struct String str_comparator_ioctet={ str_comparator_ioctet_c, 18 };
126 static uschar str_comparator_iascii_numeric_c[]="comparator-i;ascii-numeric";
127 static const struct String str_comparator_iascii_numeric={ str_comparator_iascii_numeric_c, 26 };
130 /*************************************************
131 * Encode to quoted-printable *
132 *************************************************/
139 static struct String *quoted_printable_encode(const struct String *src, struct String *dst)
142 const uschar *start,*end;
147 for (pass=0; pass<=1; ++pass)
154 dst->character=store_get(dst->length+1); /* plus one for \0 */
157 for (start=src->character,end=start+src->length; start<end; ++start)
174 || (ch>=62 && ch<=126)
180 && (*(start+1)!='\r' || *(start+2)!='\n')
195 else if (ch=='\r' && start+1<end && *(start+1)=='\n')
223 sprintf(CS new,"=%02X",ch);
230 *new='\0'; /* not included in length, but nice */
235 /*************************************************
236 * Octet-wise string comparison *
237 *************************************************/
241 needle UTF-8 string to search ...
242 haystack ... inside the haystack
243 match_prefix 1 to compare if needle is a prefix of haystack
245 Returns: 0 needle not found in haystack
249 static int eq_octet(const struct String *needle,
250 const struct String *haystack, int match_prefix)
258 h=haystack->character;
262 if (*n&0x80) return 0;
263 if (*h&0x80) return 0;
265 if (*n!=*h) return 0;
271 return (match_prefix ? nl==0 : nl==0 && hl==0);
275 /*************************************************
276 * ASCII case-insensitive string comparison *
277 *************************************************/
281 needle UTF-8 string to search ...
282 haystack ... inside the haystack
283 match_prefix 1 to compare if needle is a prefix of haystack
285 Returns: 0 needle not found in haystack
289 static int eq_asciicase(const struct String *needle,
290 const struct String *haystack, int match_prefix)
299 h=haystack->character;
305 if (nc&0x80) return 0;
306 if (hc&0x80) return 0;
308 /* tolower depends on the locale and only ASCII case must be insensitive */
309 if ((nc&0x80) || (hc&0x80)) { if (nc!=hc) return 0; }
310 else if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
316 return (match_prefix ? nl==0 : nl==0 && hl==0);
320 /*************************************************
321 * Octet-wise glob pattern search *
322 *************************************************/
326 needle pattern to search ...
327 haystack ... inside the haystack
329 Returns: 0 needle not found in haystack
333 static int eq_octetglob(const struct String *needle,
334 const struct String *haystack)
342 switch (n.character[0])
350 /* The greedy match is not yet well tested. Some day we may */
351 /* need to refer to the matched parts, so the code is already */
352 /* prepared for that. */
355 currentLength=h.length;
356 h.character+=h.length;
358 while (h.length<=currentLength)
360 if (eq_octetglob(&n,&h)) return 1;
361 else /* go back one octet */
372 if (eq_octetglob(&n,&h)) return 1;
373 else /* advance one octet */
406 (h.character[0]&0x80) || (n.character[0]&0x80) ||
408 h.character[0]!=n.character[0]
420 return (h.length==0);
424 /*************************************************
425 * ASCII case-insensitive glob pattern search *
426 *************************************************/
430 needle UTF-8 pattern to search ...
431 haystack ... inside the haystack
433 Returns: 0 needle not found in haystack
437 static int eq_asciicaseglob(const struct String *needle,
438 const struct String *haystack)
446 switch (n.character[0])
454 /* The greedy match is not yet well tested. Some day we may */
455 /* need to refer to the matched parts, so the code is already */
456 /* prepared for that. */
459 currentLength=h.length;
460 h.character+=h.length;
462 while (h.length<=currentLength)
464 if (eq_asciicaseglob(&n,&h)) return 1;
465 else /* go back one UTF-8 character */
467 if (h.length==currentLength) return 0;
470 if (h.character[0]&0x80)
472 while (h.length<currentLength && (*(h.character-1)&0x80))
484 if (eq_asciicaseglob(&n,&h)) return 1;
485 else /* advance one UTF-8 character */
487 if (h.character[0]&0x80)
489 while (h.length && (h.character[0]&0x80))
511 /* advance one UTF-8 character */
512 if (h.character[0]&0x80)
514 while (h.length && (h.character[0]&0x80))
539 if (h.length==0) return 0;
543 if ((hc&0x80) || (nc&0x80)) return 0;
545 /* tolower depends on the locale and only ASCII case must be insensitive */
546 if ((nc&0x80) || (hc&0x80)) { if (nc!=hc) return 0; }
547 else if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
555 return (h.length==0);
559 /*************************************************
560 * ASCII numeric comparison *
561 *************************************************/
565 a first numeric string
566 b second numeric string
567 relop relational operator
569 Returns: 0 not (a relop b)
573 static int eq_asciinumeric(const struct String *a,
574 const struct String *b, enum RelOp relop)
577 const uschar *as,*aend,*bs,*bend;
581 aend=a->character+a->length;
583 bend=b->character+b->length;
585 while (*as>='0' && *as<='9' && as<aend) ++as;
587 while (*bs>='0' && *bs<='9' && bs<bend) ++bs;
590 if (al && bl==0) cmp=-1;
591 else if (al==0 && bl==0) cmp=0;
592 else if (al==0 && bl) cmp=1;
596 if (cmp==0) cmp=memcmp(a->character,b->character,al);
600 case LT: return cmp<0;
601 case LE: return cmp<=0;
602 case EQ: return cmp==0;
603 case GE: return cmp>=0;
604 case GT: return cmp>0;
605 case NE: return cmp!=0;
612 /*************************************************
614 *************************************************/
618 needle UTF-8 pattern or string to search ...
619 haystack ... inside the haystack
623 Returns: 0 needle not found in haystack
625 -1 comparator does not offer matchtype
628 static int compare(struct Sieve *filter, const struct String *needle, const struct String *haystack,
629 enum Comparator co, enum MatchType mt)
633 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
634 (debug_selector & D_filter) != 0)
636 debug_printf("String comparison (match ");
639 case MATCH_IS: debug_printf(":is"); break;
640 case MATCH_CONTAINS: debug_printf(":contains"); break;
641 case MATCH_MATCHES: debug_printf(":matches"); break;
643 debug_printf(", comparison \"");
646 case COMP_OCTET: debug_printf("i;octet"); break;
647 case COMP_ASCII_CASEMAP: debug_printf("i;ascii-casemap"); break;
648 case COMP_ASCII_NUMERIC: debug_printf("i;ascii-numeric"); break;
650 debug_printf("\"):\n");
651 debug_printf(" Search = %s (%d chars)\n", needle->character,needle->length);
652 debug_printf(" Inside = %s (%d chars)\n", haystack->character,haystack->length);
662 if (eq_octet(needle,haystack,0)) r=1;
665 case COMP_ASCII_CASEMAP:
667 if (eq_asciicase(needle,haystack,0)) r=1;
670 case COMP_ASCII_NUMERIC:
672 if (!filter->require_iascii_numeric)
674 filter->errmsg=CUS "missing previous require \"comparator-i;ascii-numeric\";";
677 if (eq_asciinumeric(needle,haystack,EQ)) r=1;
691 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_octet(needle,&h,1)) { r=1; break; }
694 case COMP_ASCII_CASEMAP:
696 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_asciicase(needle,&h,1)) { r=1; break; }
701 filter->errmsg=CUS "comparator does not offer specified matchtype";
713 if (eq_octetglob(needle,haystack)) r=1;
716 case COMP_ASCII_CASEMAP:
718 if (eq_asciicaseglob(needle,haystack)) r=1;
723 filter->errmsg=CUS "comparator does not offer specified matchtype";
730 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
731 (debug_selector & D_filter) != 0)
732 debug_printf(" Result %s\n",r?"true":"false");
737 /*************************************************
738 * Check header field syntax *
739 *************************************************/
742 RFC 2822, section 3.6.8 says:
746 ftext = %d33-57 / ; Any character except
747 %d59-126 ; controls, SP, and
750 That forbids 8-bit header fields. This implementation accepts them, since
751 all of Exim is 8-bit clean, so it adds %d128-%d255.
754 header header field to quote for suitable use in Exim expansions
756 Returns: 0 string is not a valid header field
757 1 string is a value header field
760 static int is_header(const struct String *header)
770 if (((unsigned char)*h)<33 || ((unsigned char)*h)==':' || ((unsigned char)*h)==127) return 0;
781 /*************************************************
782 * Quote special characters string *
783 *************************************************/
787 header header field to quote for suitable use in Exim expansions
790 Returns: quoted string
793 static const uschar *quote(const struct String *header)
808 quoted=string_cat(quoted,&size,&ptr,CUS "\\0",2);
815 quoted=string_cat(quoted,&size,&ptr,CUS "\\",1);
819 quoted=string_cat(quoted,&size,&ptr,h,1);
825 quoted=string_cat(quoted,&size,&ptr,CUS "",1);
830 /*************************************************
831 * Add address to list of generated addresses *
832 *************************************************/
835 According to RFC 3028, duplicate delivery to the same address must
836 not happen, so the list is first searched for the address.
839 generated list of generated addresses
840 addr new address to add
841 file address denotes a file
846 static void add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
848 address_item *new_addr;
850 for (new_addr=*generated; new_addr; new_addr=new_addr->next)
852 if (Ustrcmp(new_addr->address,addr)==0 && (file ? testflag(new_addr, af_pfr|af_file) : 1))
854 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
856 debug_printf("Repeated %s `%s' ignored.\n",file ? "fileinto" : "redirect", addr);
862 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
864 debug_printf("%s `%s'\n",file ? "fileinto" : "redirect", addr);
866 new_addr=deliver_make_addr(addr,TRUE);
869 setflag(new_addr, af_pfr|af_file);
872 new_addr->p.errors_address = NULL;
873 new_addr->next = *generated;
874 *generated = new_addr;
878 /*************************************************
879 * Return decoded header field *
880 *************************************************/
884 value returned value of the field
885 header name of the header field
887 Returns: nothing The expanded string is empty
888 in case there is no such header
891 static void expand_header(struct String *value, const struct String *header)
897 value->character=(uschar*)0;
899 t=r=s=expand_string(string_sprintf("$rheader_%s",quote(header)));
906 while (*r==' ' || *r=='\t') ++r;
913 value->character=rfc2047_decode(s,TRUE,US"utf-8",'\0',&value->length,&errmsg);
917 /*************************************************
918 * Parse remaining hash comment *
919 *************************************************/
923 Comment up to terminating CRLF
926 filter points to the Sieve filter including its state
932 static int parse_hashcomment(struct Sieve *filter)
938 if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
940 if (*filter->pc=='\n')
953 filter->errmsg=CUS "missing end of comment";
958 /*************************************************
959 * Parse remaining C-style comment *
960 *************************************************/
964 Everything up to star slash
967 filter points to the Sieve filter including its state
973 static int parse_comment(struct Sieve *filter)
978 if (*filter->pc=='*' && *(filter->pc+1)=='/')
985 filter->errmsg=CUS "missing end of comment";
990 /*************************************************
991 * Parse optional white space *
992 *************************************************/
996 Spaces, tabs, CRLFs, hash comments or C-style comments
999 filter points to the Sieve filter including its state
1005 static int parse_white(struct Sieve *filter)
1009 if (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
1011 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
1013 else if (*filter->pc=='\n')
1023 else if (*filter->pc=='#')
1025 if (parse_hashcomment(filter)==-1) return -1;
1027 else if (*filter->pc=='/' && *(filter->pc+1)=='*')
1029 if (parse_comment(filter)==-1) return -1;
1037 /*************************************************
1038 * Parse a optional string *
1039 *************************************************/
1043 quoted-string = DQUOTE *CHAR DQUOTE
1044 ;; in general, \ CHAR inside a string maps to CHAR
1045 ;; so \" maps to " and \\ maps to \
1046 ;; note that newlines and other characters are all allowed
1049 multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
1050 *(multi-line-literal / multi-line-dotstuff)
1052 multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
1053 multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
1054 ;; A line containing only "." ends the multi-line.
1055 ;; Remove a leading '.' if followed by another '.'.
1056 string = quoted-string / multi-line
1059 filter points to the Sieve filter including its state
1060 id specifies identifier to match
1064 0 identifier not matched
1067 static int parse_string(struct Sieve *filter, struct String *data)
1072 data->character=(uschar*)0;
1073 if (*filter->pc=='"') /* quoted string */
1078 if (*filter->pc=='"') /* end of string */
1080 int foo=data->length;
1083 data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
1086 else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
1088 if (*(filter->pc+1)=='0') data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
1089 else data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc+1,1);
1092 else /* regular character */
1094 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1098 filter->errmsg=CUS "missing end of string";
1101 else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
1104 /* skip optional white space followed by hashed comment or CRLF */
1105 while (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
1106 if (*filter->pc=='#')
1108 if (parse_hashcomment(filter)==-1) return -1;
1111 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
1113 else if (*filter->pc=='\n')
1125 filter->errmsg=CUS "syntax error";
1131 if (*filter->pc=='\r' && *(filter->pc+1)=='\n') /* end of line */
1133 if (*filter->pc=='\n') /* end of line */
1136 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
1144 if (*filter->pc=='.' && *(filter->pc+1)=='\r' && *(filter->pc+2)=='\n') /* end of string */
1146 if (*filter->pc=='.' && *(filter->pc+1)=='\n') /* end of string */
1149 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
1158 else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
1160 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS ".",1);
1164 else /* regular character */
1166 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1170 filter->errmsg=CUS "missing end of multi line string";
1177 /*************************************************
1178 * Parse a specific identifier *
1179 *************************************************/
1183 identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
1186 filter points to the Sieve filter including its state
1187 id specifies identifier to match
1190 0 identifier not matched
1193 static int parse_identifier(struct Sieve *filter, const uschar *id)
1195 size_t idlen=Ustrlen(id);
1197 if (Ustrncmp(filter->pc,id,idlen)==0)
1199 uschar next=filter->pc[idlen];
1201 if ((next>='A' && next<='Z') || (next>='a' && next<='z') || next=='_' || (next>='0' && next<='9')) return 0;
1209 /*************************************************
1211 *************************************************/
1215 number = 1*DIGIT [QUANTIFIER]
1216 QUANTIFIER = "K" / "M" / "G"
1219 filter points to the Sieve filter including its state
1223 -1 no string list found
1226 static int parse_number(struct Sieve *filter, unsigned long *data)
1230 if (*filter->pc>='0' && *filter->pc<='9')
1235 d=Ustrtoul(filter->pc,&e,10);
1238 filter->errmsg=CUstrerror(ERANGE);
1243 if (*filter->pc=='K') { u=1024; ++filter->pc; }
1244 else if (*filter->pc=='M') { u=1024*1024; ++filter->pc; }
1245 else if (*filter->pc=='G') { u=1024*1024*1024; ++filter->pc; }
1246 if (d>(ULONG_MAX/u))
1248 filter->errmsg=CUstrerror(ERANGE);
1257 filter->errmsg=CUS "missing number";
1263 /*************************************************
1264 * Parse a string list *
1265 *************************************************/
1269 string-list = "[" string *("," string) "]" / string
1272 filter points to the Sieve filter including its state
1273 data returns string list
1276 -1 no string list found
1279 static int parse_stringlist(struct Sieve *filter, struct String **data)
1281 const uschar *orig=filter->pc;
1284 struct String *d=(struct String*)0;
1287 if (*filter->pc=='[') /* string list */
1292 if (parse_white(filter)==-1) goto error;
1293 if ((dataLength+1)>=dataCapacity) /* increase buffer */
1296 int newCapacity; /* Don't amalgamate with next line; some compilers grumble */
1297 newCapacity=dataCapacity?(dataCapacity*=2):(dataCapacity=4);
1298 if ((new=(struct String*)store_get(sizeof(struct String)*newCapacity))==(struct String*)0)
1300 filter->errmsg=CUstrerror(errno);
1303 if (d) memcpy(new,d,sizeof(struct String)*dataLength);
1305 dataCapacity=newCapacity;
1307 m=parse_string(filter,&d[dataLength]);
1310 if (dataLength==0) break;
1313 filter->errmsg=CUS "missing string";
1317 else if (m==-1) goto error;
1319 if (parse_white(filter)==-1) goto error;
1320 if (*filter->pc==',') ++filter->pc;
1323 if (*filter->pc==']')
1325 d[dataLength].character=(uschar*)0;
1326 d[dataLength].length=-1;
1333 filter->errmsg=CUS "missing closing bracket";
1337 else /* single string */
1339 if ((d=store_get(sizeof(struct String)*2))==(struct String*)0)
1343 m=parse_string(filter,&d[0]);
1355 d[1].character=(uschar*)0;
1362 filter->errmsg=CUS "missing string list";
1367 /*************************************************
1368 * Parse an optional address part specifier *
1369 *************************************************/
1373 address-part = ":localpart" / ":domain" / ":all"
1374 address-part =/ ":user" / ":detail"
1377 filter points to the Sieve filter including its state
1378 a returns address part specified
1381 0 no comparator found
1385 static int parse_addresspart(struct Sieve *filter, enum AddressPart *a)
1388 if (parse_identifier(filter,CUS ":user")==1)
1390 if (!filter->require_subaddress)
1392 filter->errmsg=CUS "missing previous require \"subaddress\";";
1398 else if (parse_identifier(filter,CUS ":detail")==1)
1400 if (!filter->require_subaddress)
1402 filter->errmsg=CUS "missing previous require \"subaddress\";";
1410 if (parse_identifier(filter,CUS ":localpart")==1)
1412 *a=ADDRPART_LOCALPART;
1415 else if (parse_identifier(filter,CUS ":domain")==1)
1420 else if (parse_identifier(filter,CUS ":all")==1)
1429 /*************************************************
1430 * Parse an optional comparator *
1431 *************************************************/
1435 comparator = ":comparator" <comparator-name: string>
1438 filter points to the Sieve filter including its state
1439 c returns comparator
1442 0 no comparator found
1443 -1 incomplete comparator found
1446 static int parse_comparator(struct Sieve *filter, enum Comparator *c)
1448 struct String comparator_name;
1450 if (parse_identifier(filter,CUS ":comparator")==0) return 0;
1451 if (parse_white(filter)==-1) return -1;
1452 switch (parse_string(filter,&comparator_name))
1457 filter->errmsg=CUS "missing comparator";
1464 if (eq_asciicase(&comparator_name,&str_ioctet,0))
1469 else if (eq_asciicase(&comparator_name,&str_iascii_casemap,0))
1471 *c=COMP_ASCII_CASEMAP;
1474 else if (eq_asciicase(&comparator_name,&str_iascii_numeric,0))
1476 *c=COMP_ASCII_NUMERIC;
1481 filter->errmsg=CUS "invalid comparator";
1490 /*************************************************
1491 * Parse an optional match type *
1492 *************************************************/
1496 match-type = ":is" / ":contains" / ":matches"
1499 filter points to the Sieve filter including its state
1500 m returns match type
1503 0 no match type found
1506 static int parse_matchtype(struct Sieve *filter, enum MatchType *m)
1508 if (parse_identifier(filter,CUS ":is")==1)
1513 else if (parse_identifier(filter,CUS ":contains")==1)
1518 else if (parse_identifier(filter,CUS ":matches")==1)
1527 /*************************************************
1528 * Parse and interpret an optional test list *
1529 *************************************************/
1533 test-list = "(" test *("," test) ")"
1536 filter points to the Sieve filter including its state
1537 n total number of tests
1538 true number of passed tests
1539 exec Execute parsed statements
1542 0 no test list found
1543 -1 syntax or execution error
1546 static int parse_testlist(struct Sieve *filter, int *n, int *true, int exec)
1548 if (parse_white(filter)==-1) return -1;
1549 if (*filter->pc=='(')
1558 switch (parse_test(filter,&cond,exec))
1561 case 0: filter->errmsg=CUS "missing test"; return -1;
1562 default: ++*n; if (cond) ++*true; break;
1564 if (parse_white(filter)==-1) return -1;
1565 if (*filter->pc==',') ++filter->pc;
1568 if (*filter->pc==')')
1575 filter->errmsg=CUS "missing closing paren";
1583 /*************************************************
1584 * Parse and interpret an optional test *
1585 *************************************************/
1589 filter points to the Sieve filter including its state
1590 cond returned condition status
1591 exec Execute parsed statements
1595 -1 syntax or execution error
1598 static int parse_test(struct Sieve *filter, int *cond, int exec)
1600 if (parse_white(filter)==-1) return -1;
1601 if (parse_identifier(filter,CUS "address"))
1604 address-test = "address" { [address-part] [comparator] [match-type] }
1605 <header-list: string-list> <key-list: string-list>
1607 header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
1610 enum AddressPart addressPart=ADDRPART_ALL;
1611 enum Comparator comparator=COMP_ASCII_CASEMAP;
1612 enum MatchType matchType=MATCH_IS;
1613 struct String *hdr,*h,*key,*k;
1619 if (parse_white(filter)==-1) return -1;
1620 if ((m=parse_addresspart(filter,&addressPart))!=0)
1622 if (m==-1) return -1;
1625 filter->errmsg=CUS "address part already specified";
1630 else if ((m=parse_comparator(filter,&comparator))!=0)
1632 if (m==-1) return -1;
1635 filter->errmsg=CUS "comparator already specified";
1640 else if ((m=parse_matchtype(filter,&matchType))!=0)
1642 if (m==-1) return -1;
1645 filter->errmsg=CUS "match type already specified";
1652 if (parse_white(filter)==-1) return -1;
1653 if ((m=parse_stringlist(filter,&hdr))!=1)
1655 if (m==0) filter->errmsg=CUS "header string list expected";
1658 if (parse_white(filter)==-1) return -1;
1659 if ((m=parse_stringlist(filter,&key))!=1)
1661 if (m==0) filter->errmsg=CUS "key string list expected";
1665 for (h=hdr; h->length!=-1 && !*cond; ++h)
1667 uschar *header_value=(uschar*)0,*extracted_addr,*end_addr;
1671 !eq_asciicase(h,&str_from,0)
1672 && !eq_asciicase(h,&str_to,0)
1673 && !eq_asciicase(h,&str_cc,0)
1674 && !eq_asciicase(h,&str_bcc,0)
1675 && !eq_asciicase(h,&str_sender,0)
1676 && !eq_asciicase(h,&str_resent_from,0)
1677 && !eq_asciicase(h,&str_resent_to,0)
1680 filter->errmsg=CUS "invalid header field";
1685 /* We are only interested in addresses below, so no MIME decoding */
1686 header_value=expand_string(string_sprintf("$rheader_%s",quote(h)));
1687 if (header_value == NULL)
1689 filter->errmsg=CUS "header string expansion failed";
1692 parse_allow_group = TRUE;
1693 while (*header_value && !*cond)
1696 int start, end, domain;
1700 end_addr = parse_find_address_end(header_value, FALSE);
1701 saveend = *end_addr;
1703 extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
1705 if (extracted_addr) switch (addressPart)
1707 case ADDRPART_ALL: part=extracted_addr; break;
1711 case ADDRPART_LOCALPART: part=extracted_addr; part[domain-1]='\0'; break;
1712 case ADDRPART_DOMAIN: part=extracted_addr+domain; break;
1714 case ADDRPART_DETAIL:
1720 *end_addr = saveend;
1723 for (k=key; k->length!=-1; ++k)
1725 struct String partStr;
1727 partStr.character=part;
1728 partStr.length=Ustrlen(part);
1731 *cond=compare(filter,k,&partStr,comparator,matchType);
1732 if (*cond==-1) return -1;
1737 if (saveend == 0) break;
1738 header_value = end_addr + 1;
1744 else if (parse_identifier(filter,CUS "allof"))
1747 allof-test = "allof" <tests: test-list>
1752 switch (parse_testlist(filter,&n,&true,exec))
1755 case 0: filter->errmsg=CUS "missing test list"; return -1;
1756 default: *cond=(n==true); return 1;
1759 else if (parse_identifier(filter,CUS "anyof"))
1762 anyof-test = "anyof" <tests: test-list>
1767 switch (parse_testlist(filter,&n,&true,exec))
1770 case 0: filter->errmsg=CUS "missing test list"; return -1;
1771 default: *cond=(true>0); return 1;
1774 else if (parse_identifier(filter,CUS "exists"))
1777 exists-test = "exists" <header-names: string-list>
1780 struct String *hdr,*h;
1783 if (parse_white(filter)==-1) return -1;
1784 if ((m=parse_stringlist(filter,&hdr))!=1)
1786 if (m==0) filter->errmsg=CUS "header string list expected";
1792 for (h=hdr; h->length!=-1 && *cond; ++h)
1796 header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
1797 if (header_def == NULL)
1799 filter->errmsg=CUS "header string expansion failed";
1802 if (Ustrcmp(header_def,"false")==0) *cond=0;
1807 else if (parse_identifier(filter,CUS "false"))
1810 false-test = "false"
1816 else if (parse_identifier(filter,CUS "header"))
1819 header-test = "header" { [comparator] [match-type] }
1820 <header-names: string-list> <key-list: string-list>
1823 enum Comparator comparator=COMP_ASCII_CASEMAP;
1824 enum MatchType matchType=MATCH_IS;
1825 struct String *hdr,*h,*key,*k;
1831 if (parse_white(filter)==-1) return -1;
1832 if ((m=parse_comparator(filter,&comparator))!=0)
1834 if (m==-1) return -1;
1837 filter->errmsg=CUS "comparator already specified";
1842 else if ((m=parse_matchtype(filter,&matchType))!=0)
1844 if (m==-1) return -1;
1847 filter->errmsg=CUS "match type already specified";
1854 if (parse_white(filter)==-1) return -1;
1855 if ((m=parse_stringlist(filter,&hdr))!=1)
1857 if (m==0) filter->errmsg=CUS "header string list expected";
1860 if (parse_white(filter)==-1) return -1;
1861 if ((m=parse_stringlist(filter,&key))!=1)
1863 if (m==0) filter->errmsg=CUS "key string list expected";
1867 for (h=hdr; h->length!=-1 && !*cond; ++h)
1871 filter->errmsg=CUS "invalid header field";
1876 struct String header_value;
1879 expand_header(&header_value,h);
1880 header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
1881 if (header_value.character == NULL || header_def == NULL)
1883 filter->errmsg=CUS "header string expansion failed";
1886 for (k=key; k->length!=-1; ++k)
1888 if (Ustrcmp(header_def,"true")==0)
1890 *cond=compare(filter,k,&header_value,comparator,matchType);
1891 if (*cond==-1) return -1;
1899 else if (parse_identifier(filter,CUS "not"))
1901 if (parse_white(filter)==-1) return -1;
1902 switch (parse_test(filter,cond,exec))
1905 case 0: filter->errmsg=CUS "missing test"; return -1;
1906 default: *cond=!*cond; return 1;
1909 else if (parse_identifier(filter,CUS "size"))
1912 relop = ":over" / ":under"
1913 size-test = "size" relop <limit: number>
1916 unsigned long limit;
1919 if (parse_white(filter)==-1) return -1;
1920 if (parse_identifier(filter,CUS ":over")) overNotUnder=1;
1921 else if (parse_identifier(filter,CUS ":under")) overNotUnder=0;
1924 filter->errmsg=CUS "missing :over or :under";
1927 if (parse_white(filter)==-1) return -1;
1928 if (parse_number(filter,&limit)==-1) return -1;
1929 *cond=(overNotUnder ? (message_size>limit) : (message_size<limit));
1932 else if (parse_identifier(filter,CUS "true"))
1937 else if (parse_identifier(filter,CUS "envelope"))
1940 envelope-test = "envelope" { [comparator] [address-part] [match-type] }
1941 <envelope-part: string-list> <key-list: string-list>
1943 envelope-part is case insensitive "from" or "to"
1946 enum Comparator comparator=COMP_ASCII_CASEMAP;
1947 enum AddressPart addressPart=ADDRPART_ALL;
1948 enum MatchType matchType=MATCH_IS;
1949 struct String *env,*e,*key,*k;
1953 if (!filter->require_envelope)
1955 filter->errmsg=CUS "missing previous require \"envelope\";";
1960 if (parse_white(filter)==-1) return -1;
1961 if ((m=parse_comparator(filter,&comparator))!=0)
1963 if (m==-1) return -1;
1966 filter->errmsg=CUS "comparator already specified";
1971 else if ((m=parse_addresspart(filter,&addressPart))!=0)
1973 if (m==-1) return -1;
1976 filter->errmsg=CUS "address part already specified";
1981 else if ((m=parse_matchtype(filter,&matchType))!=0)
1983 if (m==-1) return -1;
1986 filter->errmsg=CUS "match type already specified";
1993 if (parse_white(filter)==-1) return -1;
1994 if ((m=parse_stringlist(filter,&env))!=1)
1996 if (m==0) filter->errmsg=CUS "envelope string list expected";
1999 if (parse_white(filter)==-1) return -1;
2000 if ((m=parse_stringlist(filter,&key))!=1)
2002 if (m==0) filter->errmsg=CUS "key string list expected";
2006 for (e=env; e->character; ++e)
2008 const uschar *envelopeExpr=CUS 0;
2009 uschar *envelope=US 0;
2011 if (eq_asciicase(e,&str_from,0))
2013 switch (addressPart)
2015 case ADDRPART_ALL: envelopeExpr=CUS "$sender_address"; break;
2019 case ADDRPART_LOCALPART: envelopeExpr=CUS "${local_part:$sender_address}"; break;
2020 case ADDRPART_DOMAIN: envelopeExpr=CUS "${domain:$sender_address}"; break;
2022 case ADDRPART_DETAIL:
2028 else if (eq_asciicase(e,&str_to,0))
2030 switch (addressPart)
2032 case ADDRPART_ALL: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
2034 case ADDRPART_USER: envelopeExpr=CUS "$local_part_prefix$local_part"; break;
2035 case ADDRPART_DETAIL: envelopeExpr=CUS "$local_part_suffix"; break;
2037 case ADDRPART_LOCALPART: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix"; break;
2038 case ADDRPART_DOMAIN: envelopeExpr=CUS "$domain"; break;
2043 filter->errmsg=CUS "invalid envelope string";
2046 if (exec && envelopeExpr)
2048 if ((envelope=expand_string(US envelopeExpr)) == NULL)
2050 filter->errmsg=CUS "header string expansion failed";
2053 for (k=key; k->length!=-1; ++k)
2055 struct String envelopeStr;
2057 envelopeStr.character=envelope;
2058 envelopeStr.length=Ustrlen(envelope);
2059 *cond=compare(filter,&envelopeStr,k,comparator,matchType);
2060 if (*cond==-1) return -1;
2071 /*************************************************
2072 * Parse and interpret an optional block *
2073 *************************************************/
2077 filter points to the Sieve filter including its state
2078 exec Execute parsed statements
2079 generated where to hang newly-generated addresses
2081 Returns: 2 success by stop
2083 0 no block command found
2084 -1 syntax or execution error
2087 static int parse_block(struct Sieve *filter, int exec,
2088 address_item **generated)
2092 if (parse_white(filter)==-1) return -1;
2093 if (*filter->pc=='{')
2096 if ((r=parse_commands(filter,exec,generated))==-1 || r==2) return r;
2097 if (*filter->pc=='}')
2104 filter->errmsg=CUS "expecting command or closing brace";
2112 /*************************************************
2113 * Match a semicolon *
2114 *************************************************/
2118 filter points to the Sieve filter including its state
2124 static int parse_semicolon(struct Sieve *filter)
2126 if (parse_white(filter)==-1) return -1;
2127 if (*filter->pc==';')
2134 filter->errmsg=CUS "missing semicolon";
2140 /*************************************************
2141 * Parse and interpret a Sieve command *
2142 *************************************************/
2146 filter points to the Sieve filter including its state
2147 exec Execute parsed statements
2148 generated where to hang newly-generated addresses
2150 Returns: 2 success by stop
2152 -1 syntax or execution error
2154 static int parse_commands(struct Sieve *filter, int exec,
2155 address_item **generated)
2159 if (parse_white(filter)==-1) return -1;
2160 if (parse_identifier(filter,CUS "if"))
2163 if-command = "if" test block *( "elsif" test block ) [ else block ]
2166 int cond,m,unsuccessful;
2169 if (parse_white(filter)==-1) return -1;
2170 if ((m=parse_test(filter,&cond,exec))==-1) return -1;
2173 filter->errmsg=CUS "missing test";
2176 m=parse_block(filter,exec ? cond : 0, generated);
2177 if (m==-1 || m==2) return m;
2180 filter->errmsg=CUS "missing block";
2183 unsuccessful = !cond;
2184 for (;;) /* elsif test block */
2186 if (parse_white(filter)==-1) return -1;
2187 if (parse_identifier(filter,CUS "elsif"))
2189 if (parse_white(filter)==-1) return -1;
2190 m=parse_test(filter,&cond,exec && unsuccessful);
2191 if (m==-1 || m==2) return m;
2194 filter->errmsg=CUS "missing test";
2197 m=parse_block(filter,exec && unsuccessful ? cond : 0, generated);
2198 if (m==-1 || m==2) return m;
2201 filter->errmsg=CUS "missing block";
2204 if (exec && unsuccessful && cond) unsuccessful = 0;
2209 if (parse_white(filter)==-1) return -1;
2210 if (parse_identifier(filter,CUS "else"))
2212 m=parse_block(filter,exec && unsuccessful, generated);
2213 if (m==-1 || m==2) return m;
2216 filter->errmsg=CUS "missing block";
2221 else if (parse_identifier(filter,CUS "stop"))
2224 stop-command = "stop" { stop-options } ";"
2228 if (parse_semicolon(filter)==-1) return -1;
2231 filter->pc+=Ustrlen(filter->pc);
2235 else if (parse_identifier(filter,CUS "keep"))
2238 keep-command = "keep" { keep-options } ";"
2242 if (parse_semicolon(filter)==-1) return -1;
2245 add_addr(generated,US"inbox",1,0,0,0);
2249 else if (parse_identifier(filter,CUS "discard"))
2252 discard-command = "discard" { discard-options } ";"
2256 if (parse_semicolon(filter)==-1) return -1;
2257 if (exec) filter->keep=0;
2259 else if (parse_identifier(filter,CUS "redirect"))
2262 redirect-command = "redirect" redirect-options "string" ";"
2264 redirect-options =) ":copy"
2267 struct String recipient;
2273 if (parse_white(filter)==-1) return -1;
2274 if (parse_identifier(filter,CUS ":copy")==1)
2276 if (!filter->require_copy)
2278 filter->errmsg=CUS "missing previous require \"copy\";";
2285 if (parse_white(filter)==-1) return -1;
2286 if ((m=parse_string(filter,&recipient))!=1)
2288 if (m==0) filter->errmsg=CUS "missing redirect recipient string";
2291 if (strchr(CCS recipient.character,'@')==(char*)0)
2293 filter->errmsg=CUS "unqualified recipient address";
2298 add_addr(generated,recipient.character,0,0,0,0);
2299 if (!copy) filter->keep = 0;
2301 if (parse_semicolon(filter)==-1) return -1;
2303 else if (parse_identifier(filter,CUS "fileinto"))
2306 fileinto-command = "fileinto" { fileinto-options } string ";"
2308 fileinto-options =) [ ":copy" ]
2311 struct String folder;
2314 unsigned long maxage, maxmessages, maxstorage;
2317 maxage = maxmessages = maxstorage = 0;
2318 if (!filter->require_fileinto)
2320 filter->errmsg=CUS "missing previous require \"fileinto\";";
2325 if (parse_white(filter)==-1) return -1;
2326 if (parse_identifier(filter,CUS ":copy")==1)
2328 if (!filter->require_copy)
2330 filter->errmsg=CUS "missing previous require \"copy\";";
2337 if (parse_white(filter)==-1) return -1;
2338 if ((m=parse_string(filter,&folder))!=1)
2340 if (m==0) filter->errmsg=CUS "missing fileinto folder string";
2343 m=0; s=folder.character;
2344 if (folder.length==0) m=1;
2345 if (Ustrcmp(s,"..")==0 || Ustrncmp(s,"../",3)==0) m=1;
2348 if (Ustrcmp(s,"/..")==0 || Ustrncmp(s,"/../",4)==0) { m=1; break; }
2353 filter->errmsg=CUS "invalid folder";
2358 add_addr(generated, folder.character, 1, maxage, maxmessages, maxstorage);
2359 if (!copy) filter->keep = 0;
2361 if (parse_semicolon(filter)==-1) return -1;
2364 else if (parse_identifier(filter,CUS "vacation"))
2367 vacation-command = "vacation" { vacation-options } <reason: string> ";"
2368 vacation-options = [":days" number]
2369 [":addresses" string-list]
2376 struct String *addresses=(struct String*)0;
2377 struct String subject;
2379 string_item *aliases;
2380 struct String reason;
2382 if (!filter->require_vacation)
2384 filter->errmsg=CUS "missing previous require \"vacation\";";
2389 if (filter->vacation_ran)
2391 filter->errmsg=CUS "trying to execute vacation more than once";
2394 filter->vacation_ran=1;
2396 days=VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
2397 subject.character=(uschar*)0;
2403 if (parse_white(filter)==-1) return -1;
2404 if (parse_identifier(filter,CUS ":days")==1)
2406 if (parse_white(filter)==-1) return -1;
2407 if (parse_number(filter,&days)==-1) return -1;
2408 if (days<VACATION_MIN_DAYS) days=VACATION_MIN_DAYS;
2409 else if (days>VACATION_MAX_DAYS) days=VACATION_MAX_DAYS;
2411 else if (parse_identifier(filter,CUS ":addresses")==1)
2415 if (parse_white(filter)==-1) return -1;
2416 if ((m=parse_stringlist(filter,&addresses))!=1)
2418 if (m==0) filter->errmsg=CUS "addresses string list expected";
2421 for (a=addresses; a->length!=-1; ++a)
2425 new=store_get(sizeof(string_item));
2426 new->text=store_get(a->length+1);
2427 if (a->length) memcpy(new->text,a->character,a->length);
2428 new->text[a->length]='\0';
2433 else if (parse_identifier(filter,CUS ":subject")==1)
2435 if (parse_white(filter)==-1) return -1;
2436 if ((m=parse_string(filter,&subject))!=1)
2438 if (m==0) filter->errmsg=CUS "subject string expected";
2442 else if (parse_identifier(filter,CUS ":mime")==1)
2446 if (parse_white(filter)==-1) return -1;
2447 if ((m=parse_string(filter,&reason))!=1)
2449 if (m==0) filter->errmsg=CUS "missing reason string";
2452 if (parse_semicolon(filter)==-1) return -1;
2459 int buffer_capacity;
2463 uschar hexdigest[33];
2467 if (filter_personal(aliases,TRUE))
2470 /* ensure oncelog directory exists; failure will be detected later */
2472 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
2474 /* build oncelog filename */
2476 key.character=(uschar*)0;
2479 if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
2480 key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
2481 key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
2483 md5_end(&base, key.character, key.length, digest);
2484 for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
2485 capacity=Ustrlen(filter->vacation_directory);
2487 once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
2488 once=string_cat(once,&capacity,&start,hexdigest,33);
2490 /* process subject */
2492 if (subject.length==-1)
2494 expand_header(&subject,&str_subject);
2495 while (subject.length>=4 && Ustrncmp(subject.character,"Re: ",4)==0)
2497 subject.character+=4;
2502 subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
2503 subject.length=start;
2506 /* add address to list of generated addresses */
2508 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
2509 setflag(addr, af_pfr);
2510 setflag(addr, af_ignore_error);
2511 addr->next = *generated;
2513 addr->reply = store_get(sizeof(reply_item));
2514 memset(addr->reply,0,sizeof(reply_item)); /* XXX */
2515 addr->reply->to = string_copy(sender_address);
2516 addr->reply->from = expand_string(US"$local_part@$domain");
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");