1 /* $Cambridge: exim/src/src/sieve.c,v 1.8 2005/03/01 10:21:44 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;
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 addresses=(struct String*)0;
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;
2465 uschar hexdigest[33];
2469 if (filter_personal(aliases,TRUE))
2471 if (filter_test == FTEST_NONE)
2473 /* ensure oncelog directory exists; failure will be detected later */
2475 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
2477 /* build oncelog filename */
2479 key.character=(uschar*)0;
2482 if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
2483 key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
2484 key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
2485 if (addresses!=(struct String*)0) for (a=addresses; a->length!=-1; ++a)
2487 key.character=string_cat(key.character,&capacity,&key.length,US":",1);
2488 key.character=string_cat(key.character,&capacity,&key.length,a->character,a->length);
2491 md5_end(&base, key.character, key.length, digest);
2492 for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
2493 if (filter_test != FTEST_NONE)
2495 debug_printf("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
2499 capacity=Ustrlen(filter->vacation_directory);
2501 once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
2502 once=string_cat(once,&capacity,&start,hexdigest,33);
2505 /* process subject */
2507 if (subject.length==-1)
2509 expand_header(&subject,&str_subject);
2510 while (subject.length>=4 && Ustrncmp(subject.character,"Re: ",4)==0)
2512 subject.character+=4;
2517 subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
2518 subject.length=start;
2521 /* add address to list of generated addresses */
2523 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
2524 setflag(addr, af_pfr);
2525 setflag(addr, af_ignore_error);
2526 addr->next = *generated;
2528 addr->reply = store_get(sizeof(reply_item));
2529 memset(addr->reply,0,sizeof(reply_item)); /* XXX */
2530 addr->reply->to = string_copy(sender_address);
2531 addr->reply->from = expand_string(US"$local_part@$domain");
2532 /* Allocation is larger than neccessary, but enough even for split MIME words */
2533 buffer_capacity=16+4*subject.length;
2534 buffer=store_get(buffer_capacity);
2535 addr->reply->subject=parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity);
2536 addr->reply->oncelog=once;
2537 addr->reply->once_repeat=days*86400;
2539 /* build body and MIME headers */
2543 uschar *mime_body,*reason_end;
2545 static const uschar nlnl[]="\r\n\r\n";
2547 static const uschar nlnl[]="\n\n";
2552 mime_body=reason.character,reason_end=reason.character+reason.length;
2553 mime_body<(reason_end-sizeof(nlnl)-1) && memcmp(mime_body,nlnl,sizeof(nlnl)-1);
2558 addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
2559 addr->reply->headers[start] = '\0';
2562 if (mime_body+(sizeof(nlnl)-1)<reason_end) mime_body+=sizeof(nlnl)-1;
2563 else mime_body=reason_end-1;
2564 addr->reply->text = string_cat(NULL,&capacity,&start,mime_body,reason_end-mime_body);
2565 addr->reply->text[start] = '\0';
2572 start = reason.length;
2573 addr->reply->headers = US"MIME-Version: 1.0\n"
2574 "Content-Type: text/plain;\n"
2575 "\tcharset=\"utf-8\"\n"
2576 "Content-Transfer-Encoding: quoted-printable";
2577 addr->reply->text = quoted_printable_encode(&reason,&qp)->character;
2581 else if (filter_test != FTEST_NONE)
2583 debug_printf("Sieve: mail was not personal, vacation would ignore it\n");
2594 /*************************************************
2595 * Parse and interpret a sieve filter *
2596 *************************************************/
2600 filter points to the Sieve filter including its state
2601 exec Execute parsed statements
2602 generated where to hang newly-generated addresses
2605 -1 syntax or execution error
2608 static int parse_start(struct Sieve *filter, int exec,
2609 address_item **generated)
2611 filter->pc=filter->filter;
2614 filter->require_envelope=0;
2615 filter->require_fileinto=0;
2617 filter->require_subaddress=0;
2620 filter->require_vacation=0;
2621 filter->vacation_ran=0;
2623 filter->require_copy=0;
2624 filter->require_iascii_numeric=0;
2626 if (parse_white(filter)==-1) return -1;
2628 if (exec && filter->vacation_directory != NULL && filter_test == FTEST_NONE)
2631 struct dirent *oncelog;
2632 struct stat properties;
2635 /* clean up old vacation log databases */
2637 oncelogdir=opendir(CS filter->vacation_directory);
2639 if (oncelogdir ==(DIR*)0 && errno != ENOENT)
2641 filter->errmsg=CUS "unable to open vacation directory";
2645 if (oncelogdir != NULL)
2649 while ((oncelog=readdir(oncelogdir))!=(struct dirent*)0)
2651 if (strlen(oncelog->d_name)==32)
2653 uschar *s=string_sprintf("%s/%s",filter->vacation_directory,oncelog->d_name);
2654 if (Ustat(s,&properties)==0 && (properties.st_mtime+VACATION_MAX_DAYS*86400)<now)
2658 closedir(oncelogdir);
2662 while (parse_identifier(filter,CUS "require"))
2665 require-command = "require" <capabilities: string-list>
2668 struct String *cap,*check;
2671 if (parse_white(filter)==-1) return -1;
2672 if ((m=parse_stringlist(filter,&cap))!=1)
2674 if (m==0) filter->errmsg=CUS "capability string list expected";
2677 for (check=cap; check->character; ++check)
2679 if (eq_asciicase(check,&str_envelope,0)) filter->require_envelope=1;
2680 else if (eq_asciicase(check,&str_fileinto,0)) filter->require_fileinto=1;
2682 else if (eq_asciicase(check,&str_subaddress,0)) filter->require_subaddress=1;
2685 else if (eq_asciicase(check,&str_vacation,0))
2687 if (filter_test == FTEST_NONE && filter->vacation_directory == NULL)
2689 filter->errmsg=CUS "vacation disabled";
2692 filter->require_vacation=1;
2695 else if (eq_asciicase(check,&str_copy,0)) filter->require_copy=1;
2696 else if (eq_asciicase(check,&str_comparator_ioctet,0)) ;
2697 else if (eq_asciicase(check,&str_comparator_iascii_casemap,0)) ;
2698 else if (eq_asciicase(check,&str_comparator_iascii_numeric,0)) filter->require_iascii_numeric=1;
2701 filter->errmsg=CUS "unknown capability";
2705 if (parse_semicolon(filter)==-1) return -1;
2707 if (parse_commands(filter,exec,generated)==-1) return -1;
2710 filter->errmsg=CUS "syntax error";
2717 /*************************************************
2718 * Interpret a sieve filter file *
2719 *************************************************/
2723 filter points to the entire file, read into store as a single string
2724 options controls whether various special things are allowed, and requests
2725 special actions (not currently used)
2726 sieve_vacation_directory where to store vacation "once" files
2727 generated where to hang newly-generated addresses
2728 error where to pass back an error text
2730 Returns: FF_DELIVERED success, a significant action was taken
2731 FF_NOTDELIVERED success, no significant action
2732 FF_DEFER defer requested
2733 FF_FAIL fail requested
2734 FF_FREEZE freeze requested
2735 FF_ERROR there was a problem
2739 sieve_interpret(uschar *filter, int options, uschar *vacation_directory,
2740 address_item **generated, uschar **error)
2746 options = options; /* Keep picky compilers happy */
2749 DEBUG(D_route) debug_printf("Sieve: start of processing\n");
2750 sieve.filter=filter;
2752 if (vacation_directory == NULL)
2753 sieve.vacation_directory = NULL;
2756 sieve.vacation_directory=expand_string(vacation_directory);
2757 if (sieve.vacation_directory == NULL)
2759 *error = string_sprintf("failed to expand \"%s\" "
2760 "(sieve_vacation_directory): %s", vacation_directory,
2761 expand_string_message);
2766 #ifdef COMPILE_SYNTAX_CHECKER
2767 if (parse_start(&sieve,0,generated)==1)
2769 if (parse_start(&sieve,1,generated)==1)
2774 add_addr(generated,US"inbox",1,0,0,0);
2775 msg = string_sprintf("Keep");
2780 msg = string_sprintf("No keep");
2786 msg = string_sprintf("Sieve error: %s in line %d",sieve.errmsg,sieve.line);
2787 #ifdef COMPILE_SYNTAX_CHECKER
2791 add_addr(generated,US"inbox",1,0,0,0);
2796 #ifndef COMPILE_SYNTAX_CHECKER
2797 if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
2798 else debug_printf("%s\n", msg);
2801 DEBUG(D_route) debug_printf("Sieve: end of processing\n");