1 /* $Cambridge: exim/src/src/sieve.c,v 1.10 2005/04/07 10:02:02 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) Michael Haardt 2003-2005 */
8 /* See the file NOTICE for conditions of use and distribution. */
10 /* This code was contributed by Michael Haardt. */
13 /* Sieve mail filter. */
27 /* Define this for RFC compliant \r\n end-of-line terminators. */
28 /* Undefine it for UNIX-style \n end-of-line terminators (default). */
31 /* Define this for development of the subaddress Sieve extension. */
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;
64 const uschar *subaddress;
65 const uschar *useraddress;
67 int require_iascii_numeric;
70 enum Comparator { COMP_OCTET, COMP_ASCII_CASEMAP, COMP_ASCII_NUMERIC };
71 enum MatchType { MATCH_IS, MATCH_CONTAINS, MATCH_MATCHES };
73 enum AddressPart { ADDRPART_USER, ADDRPART_DETAIL, ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
75 enum AddressPart { ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
77 enum RelOp { LT, LE, EQ, GE, GT, NE };
85 static int parse_test(struct Sieve *filter, int *cond, int exec);
86 static int parse_commands(struct Sieve *filter, int exec, address_item **generated);
88 static uschar str_from_c[]="From";
89 static const struct String str_from={ str_from_c, 4 };
90 static uschar str_to_c[]="To";
91 static const struct String str_to={ str_to_c, 2 };
92 static uschar str_cc_c[]="Cc";
93 static const struct String str_cc={ str_cc_c, 2 };
94 static uschar str_bcc_c[]="Bcc";
95 static const struct String str_bcc={ str_bcc_c, 3 };
96 static uschar str_sender_c[]="Sender";
97 static const struct String str_sender={ str_sender_c, 6 };
98 static uschar str_resent_from_c[]="Resent-From";
99 static const struct String str_resent_from={ str_resent_from_c, 11 };
100 static uschar str_resent_to_c[]="Resent-To";
101 static const struct String str_resent_to={ str_resent_to_c, 9 };
102 static uschar str_fileinto_c[]="fileinto";
103 static const struct String str_fileinto={ str_fileinto_c, 8 };
104 static uschar str_envelope_c[]="envelope";
105 static const struct String str_envelope={ str_envelope_c, 8 };
107 static uschar str_subaddress_c[]="subaddress";
108 static const struct String str_subaddress={ str_subaddress_c, 10 };
111 static uschar str_vacation_c[]="vacation";
112 static const struct String str_vacation={ str_vacation_c, 8 };
113 static uschar str_subject_c[]="Subject";
114 static const struct String str_subject={ str_subject_c, 7 };
116 static uschar str_copy_c[]="copy";
117 static const struct String str_copy={ str_copy_c, 4 };
118 static uschar str_iascii_casemap_c[]="i;ascii-casemap";
119 static const struct String str_iascii_casemap={ str_iascii_casemap_c, 15 };
120 static uschar str_ioctet_c[]="i;octet";
121 static const struct String str_ioctet={ str_ioctet_c, 7 };
122 static uschar str_iascii_numeric_c[]="i;ascii-numeric";
123 static const struct String str_iascii_numeric={ str_iascii_numeric_c, 15 };
124 static uschar str_comparator_iascii_casemap_c[]="comparator-i;ascii-casemap";
125 static const struct String str_comparator_iascii_casemap={ str_comparator_iascii_casemap_c, 26 };
126 static uschar str_comparator_ioctet_c[]="comparator-i;octet";
127 static const struct String str_comparator_ioctet={ str_comparator_ioctet_c, 18 };
128 static uschar str_comparator_iascii_numeric_c[]="comparator-i;ascii-numeric";
129 static const struct String str_comparator_iascii_numeric={ str_comparator_iascii_numeric_c, 26 };
132 /*************************************************
133 * Encode to quoted-printable *
134 *************************************************/
141 static struct String *quoted_printable_encode(const struct String *src, struct String *dst)
144 const uschar *start,*end;
149 for (pass=0; pass<=1; ++pass)
156 dst->character=store_get(dst->length+1); /* plus one for \0 */
159 for (start=src->character,end=start+src->length; start<end; ++start)
176 || (ch>=62 && ch<=126)
182 && (*(start+1)!='\r' || *(start+2)!='\n')
197 else if (ch=='\r' && start+1<end && *(start+1)=='\n')
225 sprintf(CS new,"=%02X",ch);
232 *new='\0'; /* not included in length, but nice */
237 /*************************************************
238 * Octet-wise string comparison *
239 *************************************************/
243 needle UTF-8 string to search ...
244 haystack ... inside the haystack
245 match_prefix 1 to compare if needle is a prefix of haystack
247 Returns: 0 needle not found in haystack
251 static int eq_octet(const struct String *needle,
252 const struct String *haystack, int match_prefix)
260 h=haystack->character;
264 if (*n&0x80) return 0;
265 if (*h&0x80) return 0;
267 if (*n!=*h) return 0;
273 return (match_prefix ? nl==0 : nl==0 && hl==0);
277 /*************************************************
278 * ASCII case-insensitive string comparison *
279 *************************************************/
283 needle UTF-8 string to search ...
284 haystack ... inside the haystack
285 match_prefix 1 to compare if needle is a prefix of haystack
287 Returns: 0 needle not found in haystack
291 static int eq_asciicase(const struct String *needle,
292 const struct String *haystack, int match_prefix)
301 h=haystack->character;
307 if (nc&0x80) return 0;
308 if (hc&0x80) return 0;
310 /* tolower depends on the locale and only ASCII case must be insensitive */
311 if ((nc&0x80) || (hc&0x80)) { if (nc!=hc) return 0; }
312 else if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
318 return (match_prefix ? nl==0 : nl==0 && hl==0);
322 /*************************************************
323 * Octet-wise glob pattern search *
324 *************************************************/
328 needle pattern to search ...
329 haystack ... inside the haystack
331 Returns: 0 needle not found in haystack
335 static int eq_octetglob(const struct String *needle,
336 const struct String *haystack)
344 switch (n.character[0])
352 /* The greedy match is not yet well tested. Some day we may */
353 /* need to refer to the matched parts, so the code is already */
354 /* prepared for that. */
357 currentLength=h.length;
358 h.character+=h.length;
360 while (h.length<=currentLength)
362 if (eq_octetglob(&n,&h)) return 1;
363 else /* go back one octet */
374 if (eq_octetglob(&n,&h)) return 1;
375 else /* advance one octet */
408 (h.character[0]&0x80) || (n.character[0]&0x80) ||
410 h.character[0]!=n.character[0]
422 return (h.length==0);
426 /*************************************************
427 * ASCII case-insensitive glob pattern search *
428 *************************************************/
432 needle UTF-8 pattern to search ...
433 haystack ... inside the haystack
435 Returns: 0 needle not found in haystack
439 static int eq_asciicaseglob(const struct String *needle,
440 const struct String *haystack)
448 switch (n.character[0])
456 /* The greedy match is not yet well tested. Some day we may */
457 /* need to refer to the matched parts, so the code is already */
458 /* prepared for that. */
461 currentLength=h.length;
462 h.character+=h.length;
464 while (h.length<=currentLength)
466 if (eq_asciicaseglob(&n,&h)) return 1;
467 else /* go back one UTF-8 character */
469 if (h.length==currentLength) return 0;
472 if (h.character[0]&0x80)
474 while (h.length<currentLength && (*(h.character-1)&0x80))
486 if (eq_asciicaseglob(&n,&h)) return 1;
487 else /* advance one UTF-8 character */
489 if (h.character[0]&0x80)
491 while (h.length && (h.character[0]&0x80))
513 /* advance one UTF-8 character */
514 if (h.character[0]&0x80)
516 while (h.length && (h.character[0]&0x80))
541 if (h.length==0) return 0;
545 if ((hc&0x80) || (nc&0x80)) return 0;
547 /* tolower depends on the locale and only ASCII case must be insensitive */
548 if ((nc&0x80) || (hc&0x80)) { if (nc!=hc) return 0; }
549 else if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
557 return (h.length==0);
561 /*************************************************
562 * ASCII numeric comparison *
563 *************************************************/
567 a first numeric string
568 b second numeric string
569 relop relational operator
571 Returns: 0 not (a relop b)
575 static int eq_asciinumeric(const struct String *a,
576 const struct String *b, enum RelOp relop)
579 const uschar *as,*aend,*bs,*bend;
583 aend=a->character+a->length;
585 bend=b->character+b->length;
587 while (*as>='0' && *as<='9' && as<aend) ++as;
589 while (*bs>='0' && *bs<='9' && bs<bend) ++bs;
592 if (al && bl==0) cmp=-1;
593 else if (al==0 && bl==0) cmp=0;
594 else if (al==0 && bl) cmp=1;
598 if (cmp==0) cmp=memcmp(a->character,b->character,al);
602 case LT: return cmp<0;
603 case LE: return cmp<=0;
604 case EQ: return cmp==0;
605 case GE: return cmp>=0;
606 case GT: return cmp>0;
607 case NE: return cmp!=0;
614 /*************************************************
616 *************************************************/
620 needle UTF-8 pattern or string to search ...
621 haystack ... inside the haystack
625 Returns: 0 needle not found in haystack
627 -1 comparator does not offer matchtype
630 static int compare(struct Sieve *filter, const struct String *needle, const struct String *haystack,
631 enum Comparator co, enum MatchType mt)
635 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
636 (debug_selector & D_filter) != 0)
638 debug_printf("String comparison (match ");
641 case MATCH_IS: debug_printf(":is"); break;
642 case MATCH_CONTAINS: debug_printf(":contains"); break;
643 case MATCH_MATCHES: debug_printf(":matches"); break;
645 debug_printf(", comparison \"");
648 case COMP_OCTET: debug_printf("i;octet"); break;
649 case COMP_ASCII_CASEMAP: debug_printf("i;ascii-casemap"); break;
650 case COMP_ASCII_NUMERIC: debug_printf("i;ascii-numeric"); break;
652 debug_printf("\"):\n");
653 debug_printf(" Search = %s (%d chars)\n", needle->character,needle->length);
654 debug_printf(" Inside = %s (%d chars)\n", haystack->character,haystack->length);
664 if (eq_octet(needle,haystack,0)) r=1;
667 case COMP_ASCII_CASEMAP:
669 if (eq_asciicase(needle,haystack,0)) r=1;
672 case COMP_ASCII_NUMERIC:
674 if (!filter->require_iascii_numeric)
676 filter->errmsg=CUS "missing previous require \"comparator-i;ascii-numeric\";";
679 if (eq_asciinumeric(needle,haystack,EQ)) r=1;
693 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_octet(needle,&h,1)) { r=1; break; }
696 case COMP_ASCII_CASEMAP:
698 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_asciicase(needle,&h,1)) { r=1; break; }
703 filter->errmsg=CUS "comparator does not offer specified matchtype";
715 if (eq_octetglob(needle,haystack)) r=1;
718 case COMP_ASCII_CASEMAP:
720 if (eq_asciicaseglob(needle,haystack)) r=1;
725 filter->errmsg=CUS "comparator does not offer specified matchtype";
732 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
733 (debug_selector & D_filter) != 0)
734 debug_printf(" Result %s\n",r?"true":"false");
739 /*************************************************
740 * Check header field syntax *
741 *************************************************/
744 RFC 2822, section 3.6.8 says:
748 ftext = %d33-57 / ; Any character except
749 %d59-126 ; controls, SP, and
752 That forbids 8-bit header fields. This implementation accepts them, since
753 all of Exim is 8-bit clean, so it adds %d128-%d255.
756 header header field to quote for suitable use in Exim expansions
758 Returns: 0 string is not a valid header field
759 1 string is a value header field
762 static int is_header(const struct String *header)
772 if (((unsigned char)*h)<33 || ((unsigned char)*h)==':' || ((unsigned char)*h)==127) return 0;
783 /*************************************************
784 * Quote special characters string *
785 *************************************************/
789 header header field to quote for suitable use in Exim expansions
792 Returns: quoted string
795 static const uschar *quote(const struct String *header)
810 quoted=string_cat(quoted,&size,&ptr,CUS "\\0",2);
817 quoted=string_cat(quoted,&size,&ptr,CUS "\\",1);
821 quoted=string_cat(quoted,&size,&ptr,h,1);
827 quoted=string_cat(quoted,&size,&ptr,CUS "",1);
832 /*************************************************
833 * Add address to list of generated addresses *
834 *************************************************/
837 According to RFC 3028, duplicate delivery to the same address must
838 not happen, so the list is first searched for the address.
841 generated list of generated addresses
842 addr new address to add
843 file address denotes a file
848 static void add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
850 address_item *new_addr;
852 for (new_addr=*generated; new_addr; new_addr=new_addr->next)
854 if (Ustrcmp(new_addr->address,addr)==0 && (file ? testflag(new_addr, af_pfr|af_file) : 1))
856 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
858 debug_printf("Repeated %s `%s' ignored.\n",file ? "fileinto" : "redirect", addr);
864 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
866 debug_printf("%s `%s'\n",file ? "fileinto" : "redirect", addr);
868 new_addr=deliver_make_addr(addr,TRUE);
871 setflag(new_addr, af_pfr|af_file);
874 new_addr->p.errors_address = NULL;
875 new_addr->next = *generated;
876 *generated = new_addr;
880 /*************************************************
881 * Return decoded header field *
882 *************************************************/
886 value returned value of the field
887 header name of the header field
889 Returns: nothing The expanded string is empty
890 in case there is no such header
893 static void expand_header(struct String *value, const struct String *header)
899 value->character=(uschar*)0;
901 t=r=s=expand_string(string_sprintf("$rheader_%s",quote(header)));
908 while (*r==' ' || *r=='\t') ++r;
915 value->character=rfc2047_decode(s,TRUE,US"utf-8",'\0',&value->length,&errmsg);
919 /*************************************************
920 * Parse remaining hash comment *
921 *************************************************/
925 Comment up to terminating CRLF
928 filter points to the Sieve filter including its state
934 static int parse_hashcomment(struct Sieve *filter)
940 if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
942 if (*filter->pc=='\n')
955 filter->errmsg=CUS "missing end of comment";
960 /*************************************************
961 * Parse remaining C-style comment *
962 *************************************************/
966 Everything up to star slash
969 filter points to the Sieve filter including its state
975 static int parse_comment(struct Sieve *filter)
980 if (*filter->pc=='*' && *(filter->pc+1)=='/')
987 filter->errmsg=CUS "missing end of comment";
992 /*************************************************
993 * Parse optional white space *
994 *************************************************/
998 Spaces, tabs, CRLFs, hash comments or C-style comments
1001 filter points to the Sieve filter including its state
1007 static int parse_white(struct Sieve *filter)
1011 if (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
1013 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
1015 else if (*filter->pc=='\n')
1025 else if (*filter->pc=='#')
1027 if (parse_hashcomment(filter)==-1) return -1;
1029 else if (*filter->pc=='/' && *(filter->pc+1)=='*')
1031 if (parse_comment(filter)==-1) return -1;
1039 /*************************************************
1040 * Parse a optional string *
1041 *************************************************/
1045 quoted-string = DQUOTE *CHAR DQUOTE
1046 ;; in general, \ CHAR inside a string maps to CHAR
1047 ;; so \" maps to " and \\ maps to \
1048 ;; note that newlines and other characters are all allowed
1051 multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
1052 *(multi-line-literal / multi-line-dotstuff)
1054 multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
1055 multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
1056 ;; A line containing only "." ends the multi-line.
1057 ;; Remove a leading '.' if followed by another '.'.
1058 string = quoted-string / multi-line
1061 filter points to the Sieve filter including its state
1062 id specifies identifier to match
1066 0 identifier not matched
1069 static int parse_string(struct Sieve *filter, struct String *data)
1074 data->character=(uschar*)0;
1075 if (*filter->pc=='"') /* quoted string */
1080 if (*filter->pc=='"') /* end of string */
1082 int foo=data->length;
1085 data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
1088 else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
1090 if (*(filter->pc+1)=='0') data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
1091 else data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc+1,1);
1094 else /* regular character */
1096 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1100 filter->errmsg=CUS "missing end of string";
1103 else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
1106 /* skip optional white space followed by hashed comment or CRLF */
1107 while (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
1108 if (*filter->pc=='#')
1110 if (parse_hashcomment(filter)==-1) return -1;
1113 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
1115 else if (*filter->pc=='\n')
1127 filter->errmsg=CUS "syntax error";
1133 if (*filter->pc=='\r' && *(filter->pc+1)=='\n') /* end of line */
1135 if (*filter->pc=='\n') /* end of line */
1138 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
1146 if (*filter->pc=='.' && *(filter->pc+1)=='\r' && *(filter->pc+2)=='\n') /* end of string */
1148 if (*filter->pc=='.' && *(filter->pc+1)=='\n') /* end of string */
1151 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
1160 else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
1162 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS ".",1);
1166 else /* regular character */
1168 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1172 filter->errmsg=CUS "missing end of multi line string";
1179 /*************************************************
1180 * Parse a specific identifier *
1181 *************************************************/
1185 identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
1188 filter points to the Sieve filter including its state
1189 id specifies identifier to match
1192 0 identifier not matched
1195 static int parse_identifier(struct Sieve *filter, const uschar *id)
1197 size_t idlen=Ustrlen(id);
1199 if (Ustrncmp(filter->pc,id,idlen)==0)
1201 uschar next=filter->pc[idlen];
1203 if ((next>='A' && next<='Z') || (next>='a' && next<='z') || next=='_' || (next>='0' && next<='9')) return 0;
1211 /*************************************************
1213 *************************************************/
1217 number = 1*DIGIT [QUANTIFIER]
1218 QUANTIFIER = "K" / "M" / "G"
1221 filter points to the Sieve filter including its state
1225 -1 no string list found
1228 static int parse_number(struct Sieve *filter, unsigned long *data)
1232 if (*filter->pc>='0' && *filter->pc<='9')
1237 d=Ustrtoul(filter->pc,&e,10);
1240 filter->errmsg=CUstrerror(ERANGE);
1245 if (*filter->pc=='K') { u=1024; ++filter->pc; }
1246 else if (*filter->pc=='M') { u=1024*1024; ++filter->pc; }
1247 else if (*filter->pc=='G') { u=1024*1024*1024; ++filter->pc; }
1248 if (d>(ULONG_MAX/u))
1250 filter->errmsg=CUstrerror(ERANGE);
1259 filter->errmsg=CUS "missing number";
1265 /*************************************************
1266 * Parse a string list *
1267 *************************************************/
1271 string-list = "[" string *("," string) "]" / string
1274 filter points to the Sieve filter including its state
1275 data returns string list
1278 -1 no string list found
1281 static int parse_stringlist(struct Sieve *filter, struct String **data)
1283 const uschar *orig=filter->pc;
1286 struct String *d=(struct String*)0;
1289 if (*filter->pc=='[') /* string list */
1294 if (parse_white(filter)==-1) goto error;
1295 if ((dataLength+1)>=dataCapacity) /* increase buffer */
1298 int newCapacity; /* Don't amalgamate with next line; some compilers grumble */
1299 newCapacity=dataCapacity?(dataCapacity*=2):(dataCapacity=4);
1300 if ((new=(struct String*)store_get(sizeof(struct String)*newCapacity))==(struct String*)0)
1302 filter->errmsg=CUstrerror(errno);
1305 if (d) memcpy(new,d,sizeof(struct String)*dataLength);
1307 dataCapacity=newCapacity;
1309 m=parse_string(filter,&d[dataLength]);
1312 if (dataLength==0) break;
1315 filter->errmsg=CUS "missing string";
1319 else if (m==-1) goto error;
1321 if (parse_white(filter)==-1) goto error;
1322 if (*filter->pc==',') ++filter->pc;
1325 if (*filter->pc==']')
1327 d[dataLength].character=(uschar*)0;
1328 d[dataLength].length=-1;
1335 filter->errmsg=CUS "missing closing bracket";
1339 else /* single string */
1341 if ((d=store_get(sizeof(struct String)*2))==(struct String*)0)
1345 m=parse_string(filter,&d[0]);
1357 d[1].character=(uschar*)0;
1364 filter->errmsg=CUS "missing string list";
1369 /*************************************************
1370 * Parse an optional address part specifier *
1371 *************************************************/
1375 address-part = ":localpart" / ":domain" / ":all"
1376 address-part =/ ":user" / ":detail"
1379 filter points to the Sieve filter including its state
1380 a returns address part specified
1383 0 no comparator found
1387 static int parse_addresspart(struct Sieve *filter, enum AddressPart *a)
1390 if (parse_identifier(filter,CUS ":user")==1)
1392 if (!filter->require_subaddress)
1394 filter->errmsg=CUS "missing previous require \"subaddress\";";
1400 else if (parse_identifier(filter,CUS ":detail")==1)
1402 if (!filter->require_subaddress)
1404 filter->errmsg=CUS "missing previous require \"subaddress\";";
1412 if (parse_identifier(filter,CUS ":localpart")==1)
1414 *a=ADDRPART_LOCALPART;
1417 else if (parse_identifier(filter,CUS ":domain")==1)
1422 else if (parse_identifier(filter,CUS ":all")==1)
1431 /*************************************************
1432 * Parse an optional comparator *
1433 *************************************************/
1437 comparator = ":comparator" <comparator-name: string>
1440 filter points to the Sieve filter including its state
1441 c returns comparator
1444 0 no comparator found
1445 -1 incomplete comparator found
1448 static int parse_comparator(struct Sieve *filter, enum Comparator *c)
1450 struct String comparator_name;
1452 if (parse_identifier(filter,CUS ":comparator")==0) return 0;
1453 if (parse_white(filter)==-1) return -1;
1454 switch (parse_string(filter,&comparator_name))
1459 filter->errmsg=CUS "missing comparator";
1466 if (eq_asciicase(&comparator_name,&str_ioctet,0))
1471 else if (eq_asciicase(&comparator_name,&str_iascii_casemap,0))
1473 *c=COMP_ASCII_CASEMAP;
1476 else if (eq_asciicase(&comparator_name,&str_iascii_numeric,0))
1478 *c=COMP_ASCII_NUMERIC;
1483 filter->errmsg=CUS "invalid comparator";
1492 /*************************************************
1493 * Parse an optional match type *
1494 *************************************************/
1498 match-type = ":is" / ":contains" / ":matches"
1501 filter points to the Sieve filter including its state
1502 m returns match type
1505 0 no match type found
1508 static int parse_matchtype(struct Sieve *filter, enum MatchType *m)
1510 if (parse_identifier(filter,CUS ":is")==1)
1515 else if (parse_identifier(filter,CUS ":contains")==1)
1520 else if (parse_identifier(filter,CUS ":matches")==1)
1529 /*************************************************
1530 * Parse and interpret an optional test list *
1531 *************************************************/
1535 test-list = "(" test *("," test) ")"
1538 filter points to the Sieve filter including its state
1539 n total number of tests
1540 true number of passed tests
1541 exec Execute parsed statements
1544 0 no test list found
1545 -1 syntax or execution error
1548 static int parse_testlist(struct Sieve *filter, int *n, int *true, int exec)
1550 if (parse_white(filter)==-1) return -1;
1551 if (*filter->pc=='(')
1560 switch (parse_test(filter,&cond,exec))
1563 case 0: filter->errmsg=CUS "missing test"; return -1;
1564 default: ++*n; if (cond) ++*true; break;
1566 if (parse_white(filter)==-1) return -1;
1567 if (*filter->pc==',') ++filter->pc;
1570 if (*filter->pc==')')
1577 filter->errmsg=CUS "missing closing paren";
1585 /*************************************************
1586 * Parse and interpret an optional test *
1587 *************************************************/
1591 filter points to the Sieve filter including its state
1592 cond returned condition status
1593 exec Execute parsed statements
1597 -1 syntax or execution error
1600 static int parse_test(struct Sieve *filter, int *cond, int exec)
1602 if (parse_white(filter)==-1) return -1;
1603 if (parse_identifier(filter,CUS "address"))
1606 address-test = "address" { [address-part] [comparator] [match-type] }
1607 <header-list: string-list> <key-list: string-list>
1609 header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
1612 enum AddressPart addressPart=ADDRPART_ALL;
1613 enum Comparator comparator=COMP_ASCII_CASEMAP;
1614 enum MatchType matchType=MATCH_IS;
1615 struct String *hdr,*h,*key,*k;
1621 if (parse_white(filter)==-1) return -1;
1622 if ((m=parse_addresspart(filter,&addressPart))!=0)
1624 if (m==-1) return -1;
1627 filter->errmsg=CUS "address part already specified";
1632 else if ((m=parse_comparator(filter,&comparator))!=0)
1634 if (m==-1) return -1;
1637 filter->errmsg=CUS "comparator already specified";
1642 else if ((m=parse_matchtype(filter,&matchType))!=0)
1644 if (m==-1) return -1;
1647 filter->errmsg=CUS "match type already specified";
1654 if (parse_white(filter)==-1) return -1;
1655 if ((m=parse_stringlist(filter,&hdr))!=1)
1657 if (m==0) filter->errmsg=CUS "header string list expected";
1660 if (parse_white(filter)==-1) return -1;
1661 if ((m=parse_stringlist(filter,&key))!=1)
1663 if (m==0) filter->errmsg=CUS "key string list expected";
1667 for (h=hdr; h->length!=-1 && !*cond; ++h)
1669 uschar *header_value=(uschar*)0,*extracted_addr,*end_addr;
1673 !eq_asciicase(h,&str_from,0)
1674 && !eq_asciicase(h,&str_to,0)
1675 && !eq_asciicase(h,&str_cc,0)
1676 && !eq_asciicase(h,&str_bcc,0)
1677 && !eq_asciicase(h,&str_sender,0)
1678 && !eq_asciicase(h,&str_resent_from,0)
1679 && !eq_asciicase(h,&str_resent_to,0)
1682 filter->errmsg=CUS "invalid header field";
1687 /* We are only interested in addresses below, so no MIME decoding */
1688 header_value=expand_string(string_sprintf("$rheader_%s",quote(h)));
1689 if (header_value == NULL)
1691 filter->errmsg=CUS "header string expansion failed";
1694 parse_allow_group = TRUE;
1695 while (*header_value && !*cond)
1698 int start, end, domain;
1702 end_addr = parse_find_address_end(header_value, FALSE);
1703 saveend = *end_addr;
1705 extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
1707 if (extracted_addr) switch (addressPart)
1709 case ADDRPART_ALL: part=extracted_addr; break;
1713 case ADDRPART_LOCALPART: part=extracted_addr; part[domain-1]='\0'; break;
1714 case ADDRPART_DOMAIN: part=extracted_addr+domain; break;
1716 case ADDRPART_DETAIL: part=NULL; break;
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: envelopeExpr=CUS 0; break;
2026 else if (eq_asciicase(e,&str_to,0))
2028 switch (addressPart)
2030 case ADDRPART_ALL: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
2032 case ADDRPART_USER: envelopeExpr=filter->useraddress; break;
2033 case ADDRPART_DETAIL: envelopeExpr=filter->subaddress; break;
2035 case ADDRPART_LOCALPART: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix"; break;
2036 case ADDRPART_DOMAIN: envelopeExpr=CUS "$domain"; break;
2041 filter->errmsg=CUS "invalid envelope string";
2044 if (exec && envelopeExpr)
2046 if ((envelope=expand_string(US envelopeExpr)) == NULL)
2048 filter->errmsg=CUS "header string expansion failed";
2051 for (k=key; k->length!=-1; ++k)
2053 struct String envelopeStr;
2055 envelopeStr.character=envelope;
2056 envelopeStr.length=Ustrlen(envelope);
2057 *cond=compare(filter,k,&envelopeStr,comparator,matchType);
2058 if (*cond==-1) return -1;
2069 /*************************************************
2070 * Parse and interpret an optional block *
2071 *************************************************/
2075 filter points to the Sieve filter including its state
2076 exec Execute parsed statements
2077 generated where to hang newly-generated addresses
2079 Returns: 2 success by stop
2081 0 no block command found
2082 -1 syntax or execution error
2085 static int parse_block(struct Sieve *filter, int exec,
2086 address_item **generated)
2090 if (parse_white(filter)==-1) return -1;
2091 if (*filter->pc=='{')
2094 if ((r=parse_commands(filter,exec,generated))==-1 || r==2) return r;
2095 if (*filter->pc=='}')
2102 filter->errmsg=CUS "expecting command or closing brace";
2110 /*************************************************
2111 * Match a semicolon *
2112 *************************************************/
2116 filter points to the Sieve filter including its state
2122 static int parse_semicolon(struct Sieve *filter)
2124 if (parse_white(filter)==-1) return -1;
2125 if (*filter->pc==';')
2132 filter->errmsg=CUS "missing semicolon";
2138 /*************************************************
2139 * Parse and interpret a Sieve command *
2140 *************************************************/
2144 filter points to the Sieve filter including its state
2145 exec Execute parsed statements
2146 generated where to hang newly-generated addresses
2148 Returns: 2 success by stop
2150 -1 syntax or execution error
2152 static int parse_commands(struct Sieve *filter, int exec,
2153 address_item **generated)
2157 if (parse_white(filter)==-1) return -1;
2158 if (parse_identifier(filter,CUS "if"))
2161 if-command = "if" test block *( "elsif" test block ) [ else block ]
2164 int cond,m,unsuccessful;
2167 if (parse_white(filter)==-1) return -1;
2168 if ((m=parse_test(filter,&cond,exec))==-1) return -1;
2171 filter->errmsg=CUS "missing test";
2174 m=parse_block(filter,exec ? cond : 0, generated);
2175 if (m==-1 || m==2) return m;
2178 filter->errmsg=CUS "missing block";
2181 unsuccessful = !cond;
2182 for (;;) /* elsif test block */
2184 if (parse_white(filter)==-1) return -1;
2185 if (parse_identifier(filter,CUS "elsif"))
2187 if (parse_white(filter)==-1) return -1;
2188 m=parse_test(filter,&cond,exec && unsuccessful);
2189 if (m==-1 || m==2) return m;
2192 filter->errmsg=CUS "missing test";
2195 m=parse_block(filter,exec && unsuccessful ? cond : 0, generated);
2196 if (m==-1 || m==2) return m;
2199 filter->errmsg=CUS "missing block";
2202 if (exec && unsuccessful && cond) unsuccessful = 0;
2207 if (parse_white(filter)==-1) return -1;
2208 if (parse_identifier(filter,CUS "else"))
2210 m=parse_block(filter,exec && unsuccessful, generated);
2211 if (m==-1 || m==2) return m;
2214 filter->errmsg=CUS "missing block";
2219 else if (parse_identifier(filter,CUS "stop"))
2222 stop-command = "stop" { stop-options } ";"
2226 if (parse_semicolon(filter)==-1) return -1;
2229 filter->pc+=Ustrlen(filter->pc);
2233 else if (parse_identifier(filter,CUS "keep"))
2236 keep-command = "keep" { keep-options } ";"
2240 if (parse_semicolon(filter)==-1) return -1;
2243 add_addr(generated,US"inbox",1,0,0,0);
2247 else if (parse_identifier(filter,CUS "discard"))
2250 discard-command = "discard" { discard-options } ";"
2254 if (parse_semicolon(filter)==-1) return -1;
2255 if (exec) filter->keep=0;
2257 else if (parse_identifier(filter,CUS "redirect"))
2260 redirect-command = "redirect" redirect-options "string" ";"
2262 redirect-options =) ":copy"
2265 struct String recipient;
2271 if (parse_white(filter)==-1) return -1;
2272 if (parse_identifier(filter,CUS ":copy")==1)
2274 if (!filter->require_copy)
2276 filter->errmsg=CUS "missing previous require \"copy\";";
2283 if (parse_white(filter)==-1) return -1;
2284 if ((m=parse_string(filter,&recipient))!=1)
2286 if (m==0) filter->errmsg=CUS "missing redirect recipient string";
2289 if (strchr(CCS recipient.character,'@')==(char*)0)
2291 filter->errmsg=CUS "unqualified recipient address";
2296 add_addr(generated,recipient.character,0,0,0,0);
2297 if (!copy) filter->keep = 0;
2299 if (parse_semicolon(filter)==-1) return -1;
2301 else if (parse_identifier(filter,CUS "fileinto"))
2304 fileinto-command = "fileinto" { fileinto-options } string ";"
2306 fileinto-options =) [ ":copy" ]
2309 struct String folder;
2312 unsigned long maxage, maxmessages, maxstorage;
2315 maxage = maxmessages = maxstorage = 0;
2316 if (!filter->require_fileinto)
2318 filter->errmsg=CUS "missing previous require \"fileinto\";";
2323 if (parse_white(filter)==-1) return -1;
2324 if (parse_identifier(filter,CUS ":copy")==1)
2326 if (!filter->require_copy)
2328 filter->errmsg=CUS "missing previous require \"copy\";";
2335 if (parse_white(filter)==-1) return -1;
2336 if ((m=parse_string(filter,&folder))!=1)
2338 if (m==0) filter->errmsg=CUS "missing fileinto folder string";
2341 m=0; s=folder.character;
2342 if (folder.length==0) m=1;
2343 if (Ustrcmp(s,"..")==0 || Ustrncmp(s,"../",3)==0) m=1;
2346 if (Ustrcmp(s,"/..")==0 || Ustrncmp(s,"/../",4)==0) { m=1; break; }
2351 filter->errmsg=CUS "invalid folder";
2356 add_addr(generated, folder.character, 1, maxage, maxmessages, maxstorage);
2357 if (!copy) filter->keep = 0;
2359 if (parse_semicolon(filter)==-1) return -1;
2362 else if (parse_identifier(filter,CUS "vacation"))
2365 vacation-command = "vacation" { vacation-options } <reason: string> ";"
2366 vacation-options = [":days" number]
2367 [":addresses" string-list]
2374 struct String *addresses;
2375 struct String subject;
2377 string_item *aliases;
2378 struct String reason;
2380 if (!filter->require_vacation)
2382 filter->errmsg=CUS "missing previous require \"vacation\";";
2387 if (filter->vacation_ran)
2389 filter->errmsg=CUS "trying to execute vacation more than once";
2392 filter->vacation_ran=1;
2394 days=VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
2395 addresses=(struct String*)0;
2396 subject.character=(uschar*)0;
2402 if (parse_white(filter)==-1) return -1;
2403 if (parse_identifier(filter,CUS ":days")==1)
2405 if (parse_white(filter)==-1) return -1;
2406 if (parse_number(filter,&days)==-1) return -1;
2407 if (days<VACATION_MIN_DAYS) days=VACATION_MIN_DAYS;
2408 else if (days>VACATION_MAX_DAYS) days=VACATION_MAX_DAYS;
2410 else if (parse_identifier(filter,CUS ":addresses")==1)
2414 if (parse_white(filter)==-1) return -1;
2415 if ((m=parse_stringlist(filter,&addresses))!=1)
2417 if (m==0) filter->errmsg=CUS "addresses string list expected";
2420 for (a=addresses; a->length!=-1; ++a)
2424 new=store_get(sizeof(string_item));
2425 new->text=store_get(a->length+1);
2426 if (a->length) memcpy(new->text,a->character,a->length);
2427 new->text[a->length]='\0';
2432 else if (parse_identifier(filter,CUS ":subject")==1)
2434 if (parse_white(filter)==-1) return -1;
2435 if ((m=parse_string(filter,&subject))!=1)
2437 if (m==0) filter->errmsg=CUS "subject string expected";
2441 else if (parse_identifier(filter,CUS ":mime")==1)
2445 if (parse_white(filter)==-1) return -1;
2446 if ((m=parse_string(filter,&reason))!=1)
2448 if (m==0) filter->errmsg=CUS "missing reason string";
2451 if (parse_semicolon(filter)==-1) return -1;
2458 int buffer_capacity;
2463 uschar hexdigest[33];
2467 if (filter_personal(aliases,TRUE))
2469 if (filter_test == FTEST_NONE)
2471 /* ensure oncelog directory exists; failure will be detected later */
2473 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
2475 /* build oncelog filename */
2477 key.character=(uschar*)0;
2480 if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
2481 key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
2482 key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
2483 if (addresses!=(struct String*)0) for (a=addresses; a->length!=-1; ++a)
2485 key.character=string_cat(key.character,&capacity,&key.length,US":",1);
2486 key.character=string_cat(key.character,&capacity,&key.length,a->character,a->length);
2489 md5_end(&base, key.character, key.length, digest);
2490 for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
2491 if (filter_test != FTEST_NONE)
2493 debug_printf("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
2497 capacity=Ustrlen(filter->vacation_directory);
2499 once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
2500 once=string_cat(once,&capacity,&start,hexdigest,33);
2503 /* process subject */
2505 if (subject.length==-1)
2507 expand_header(&subject,&str_subject);
2508 while (subject.length>=4 && Ustrncmp(subject.character,"Re: ",4)==0)
2510 subject.character+=4;
2515 subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
2516 subject.length=start;
2519 /* add address to list of generated addresses */
2521 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
2522 setflag(addr, af_pfr);
2523 setflag(addr, af_ignore_error);
2524 addr->next = *generated;
2526 addr->reply = store_get(sizeof(reply_item));
2527 memset(addr->reply,0,sizeof(reply_item)); /* XXX */
2528 addr->reply->to = string_copy(sender_address);
2529 addr->reply->from = expand_string(US"$local_part@$domain");
2530 /* Allocation is larger than neccessary, but enough even for split MIME words */
2531 buffer_capacity=16+4*subject.length;
2532 buffer=store_get(buffer_capacity);
2533 addr->reply->subject=parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity);
2534 addr->reply->oncelog=once;
2535 addr->reply->once_repeat=days*86400;
2537 /* build body and MIME headers */
2541 uschar *mime_body,*reason_end;
2543 static const uschar nlnl[]="\r\n\r\n";
2545 static const uschar nlnl[]="\n\n";
2550 mime_body=reason.character,reason_end=reason.character+reason.length;
2551 mime_body<(reason_end-sizeof(nlnl)-1) && memcmp(mime_body,nlnl,sizeof(nlnl)-1);
2556 addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
2557 addr->reply->headers[start] = '\0';
2560 if (mime_body+(sizeof(nlnl)-1)<reason_end) mime_body+=sizeof(nlnl)-1;
2561 else mime_body=reason_end-1;
2562 addr->reply->text = string_cat(NULL,&capacity,&start,mime_body,reason_end-mime_body);
2563 addr->reply->text[start] = '\0';
2570 start = reason.length;
2571 addr->reply->headers = US"MIME-Version: 1.0\n"
2572 "Content-Type: text/plain;\n"
2573 "\tcharset=\"utf-8\"\n"
2574 "Content-Transfer-Encoding: quoted-printable";
2575 addr->reply->text = quoted_printable_encode(&reason,&qp)->character;
2579 else if (filter_test != FTEST_NONE)
2581 debug_printf("Sieve: mail was not personal, vacation would ignore it\n");
2592 /*************************************************
2593 * Parse and interpret a sieve filter *
2594 *************************************************/
2598 filter points to the Sieve filter including its state
2599 exec Execute parsed statements
2600 generated where to hang newly-generated addresses
2603 -1 syntax or execution error
2606 static int parse_start(struct Sieve *filter, int exec,
2607 address_item **generated)
2609 filter->pc=filter->filter;
2612 filter->require_envelope=0;
2613 filter->require_fileinto=0;
2615 filter->require_subaddress=0;
2618 filter->require_vacation=0;
2619 filter->vacation_ran=0;
2621 filter->require_copy=0;
2622 filter->require_iascii_numeric=0;
2624 if (parse_white(filter)==-1) return -1;
2626 if (exec && filter->vacation_directory != NULL && filter_test == FTEST_NONE)
2629 struct dirent *oncelog;
2630 struct stat properties;
2633 /* clean up old vacation log databases */
2635 oncelogdir=opendir(CS filter->vacation_directory);
2637 if (oncelogdir ==(DIR*)0 && errno != ENOENT)
2639 filter->errmsg=CUS "unable to open vacation directory";
2643 if (oncelogdir != NULL)
2647 while ((oncelog=readdir(oncelogdir))!=(struct dirent*)0)
2649 if (strlen(oncelog->d_name)==32)
2651 uschar *s=string_sprintf("%s/%s",filter->vacation_directory,oncelog->d_name);
2652 if (Ustat(s,&properties)==0 && (properties.st_mtime+VACATION_MAX_DAYS*86400)<now)
2656 closedir(oncelogdir);
2660 while (parse_identifier(filter,CUS "require"))
2663 require-command = "require" <capabilities: string-list>
2666 struct String *cap,*check;
2669 if (parse_white(filter)==-1) return -1;
2670 if ((m=parse_stringlist(filter,&cap))!=1)
2672 if (m==0) filter->errmsg=CUS "capability string list expected";
2675 for (check=cap; check->character; ++check)
2677 if (eq_asciicase(check,&str_envelope,0)) filter->require_envelope=1;
2678 else if (eq_asciicase(check,&str_fileinto,0)) filter->require_fileinto=1;
2680 else if (eq_asciicase(check,&str_subaddress,0)) filter->require_subaddress=1;
2683 else if (eq_asciicase(check,&str_vacation,0))
2685 if (filter_test == FTEST_NONE && filter->vacation_directory == NULL)
2687 filter->errmsg=CUS "vacation disabled";
2690 filter->require_vacation=1;
2693 else if (eq_asciicase(check,&str_copy,0)) filter->require_copy=1;
2694 else if (eq_asciicase(check,&str_comparator_ioctet,0)) ;
2695 else if (eq_asciicase(check,&str_comparator_iascii_casemap,0)) ;
2696 else if (eq_asciicase(check,&str_comparator_iascii_numeric,0)) filter->require_iascii_numeric=1;
2699 filter->errmsg=CUS "unknown capability";
2703 if (parse_semicolon(filter)==-1) return -1;
2705 if (parse_commands(filter,exec,generated)==-1) return -1;
2708 filter->errmsg=CUS "syntax error";
2715 /*************************************************
2716 * Interpret a sieve filter file *
2717 *************************************************/
2721 filter points to the entire file, read into store as a single string
2722 options controls whether various special things are allowed, and requests
2723 special actions (not currently used)
2724 sieve_vacation_directory where to store vacation "once" files
2725 useraddress string expression for :user part of address
2726 subaddress string expression for :subaddress part of address
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 uschar *useraddress, uschar *subaddress, 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 sieve.useraddress = useraddress == NULL ? CUS "$local_part_prefix$local_part$local_part_suffix" : useraddress;
2767 sieve.subaddress = subaddress;
2769 #ifdef COMPILE_SYNTAX_CHECKER
2770 if (parse_start(&sieve,0,generated)==1)
2772 if (parse_start(&sieve,1,generated)==1)
2777 add_addr(generated,US"inbox",1,0,0,0);
2778 msg = string_sprintf("Keep");
2783 msg = string_sprintf("No keep");
2789 msg = string_sprintf("Sieve error: %s in line %d",sieve.errmsg,sieve.line);
2790 #ifdef COMPILE_SYNTAX_CHECKER
2794 add_addr(generated,US"inbox",1,0,0,0);
2799 #ifndef COMPILE_SYNTAX_CHECKER
2800 if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
2801 else debug_printf("%s\n", msg);
2804 DEBUG(D_route) debug_printf("Sieve: end of processing\n");