1 /* $Cambridge: exim/src/src/sieve.c,v 1.17 2005/11/28 09:47:20 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 Sieve extension "envelope-auth". */
34 /* Define this for development of the Sieve extension "notify". */
37 /* Define this for the Sieve extension "subaddress". */
40 /* Define this for the Sieve extension "vacation". */
44 #define VACATION_MIN_DAYS 1
45 /* Must be >= VACATION_MIN_DAYS, must be > 7, should be > 30 */
46 #define VACATION_MAX_DAYS 31
48 /* Keep this at 75 to accept only RFC compliant MIME words. */
49 /* Increase it if you want to match headers from buggy MUAs. */
50 #define MIMEWORD_LENGTH 75
62 int require_envelope_auth;
68 int require_subaddress;
74 uschar *vacation_directory;
75 const uschar *subaddress;
76 const uschar *useraddress;
78 int require_iascii_numeric;
81 enum Comparator { COMP_OCTET, COMP_EN_ASCII_CASEMAP, COMP_ASCII_NUMERIC };
82 enum MatchType { MATCH_IS, MATCH_CONTAINS, MATCH_MATCHES };
84 enum AddressPart { ADDRPART_USER, ADDRPART_DETAIL, ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
86 enum AddressPart { ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
88 enum RelOp { LT, LE, EQ, GE, GT, NE };
96 static int parse_test(struct Sieve *filter, int *cond, int exec);
97 static int parse_commands(struct Sieve *filter, int exec, address_item **generated);
99 static uschar str_from_c[]="From";
100 static const struct String str_from={ str_from_c, 4 };
101 static uschar str_to_c[]="To";
102 static const struct String str_to={ str_to_c, 2 };
103 static uschar str_cc_c[]="Cc";
104 static const struct String str_cc={ str_cc_c, 2 };
105 static uschar str_bcc_c[]="Bcc";
106 static const struct String str_bcc={ str_bcc_c, 3 };
107 static uschar str_auth_c[]="auth";
108 static const struct String str_auth={ str_auth_c, 4 };
109 static uschar str_sender_c[]="Sender";
110 static const struct String str_sender={ str_sender_c, 6 };
111 static uschar str_resent_from_c[]="Resent-From";
112 static const struct String str_resent_from={ str_resent_from_c, 11 };
113 static uschar str_resent_to_c[]="Resent-To";
114 static const struct String str_resent_to={ str_resent_to_c, 9 };
115 static uschar str_fileinto_c[]="fileinto";
116 static const struct String str_fileinto={ str_fileinto_c, 8 };
117 static uschar str_envelope_c[]="envelope";
118 static const struct String str_envelope={ str_envelope_c, 8 };
120 static uschar str_envelope_auth_c[]="envelope-auth";
121 static const struct String str_envelope_auth={ str_envelope_auth_c, 13 };
124 static uschar str_notify_c[]="notify";
125 static const struct String str_notify={ str_notify_c, 6 };
128 static uschar str_subaddress_c[]="subaddress";
129 static const struct String str_subaddress={ str_subaddress_c, 10 };
132 static uschar str_vacation_c[]="vacation";
133 static const struct String str_vacation={ str_vacation_c, 8 };
134 static uschar str_subject_c[]="Subject";
135 static const struct String str_subject={ str_subject_c, 7 };
137 static uschar str_copy_c[]="copy";
138 static const struct String str_copy={ str_copy_c, 4 };
139 static uschar str_iascii_casemap_c[]="i;ascii-casemap";
140 static const struct String str_iascii_casemap={ str_iascii_casemap_c, 15 };
141 static uschar str_enascii_casemap_c[]="en;ascii-casemap";
142 static const struct String str_enascii_casemap={ str_enascii_casemap_c, 16 };
143 static uschar str_ioctet_c[]="i;octet";
144 static const struct String str_ioctet={ str_ioctet_c, 7 };
145 static uschar str_iascii_numeric_c[]="i;ascii-numeric";
146 static const struct String str_iascii_numeric={ str_iascii_numeric_c, 15 };
147 static uschar str_comparator_iascii_casemap_c[]="comparator-i;ascii-casemap";
148 static const struct String str_comparator_iascii_casemap={ str_comparator_iascii_casemap_c, 26 };
149 static uschar str_comparator_enascii_casemap_c[]="comparator-en;ascii-casemap";
150 static const struct String str_comparator_enascii_casemap={ str_comparator_enascii_casemap_c, 27 };
151 static uschar str_comparator_ioctet_c[]="comparator-i;octet";
152 static const struct String str_comparator_ioctet={ str_comparator_ioctet_c, 18 };
153 static uschar str_comparator_iascii_numeric_c[]="comparator-i;ascii-numeric";
154 static const struct String str_comparator_iascii_numeric={ str_comparator_iascii_numeric_c, 26 };
157 /*************************************************
158 * Encode to quoted-printable *
159 *************************************************/
166 static struct String *quoted_printable_encode(const struct String *src, struct String *dst)
169 const uschar *start,*end;
174 for (pass=0; pass<=1; ++pass)
181 dst->character=store_get(dst->length+1); /* plus one for \0 */
184 for (start=src->character,end=start+src->length; start<end; ++start)
201 || (ch>=62 && ch<=126)
207 && (*(start+1)!='\r' || *(start+2)!='\n')
222 else if (ch=='\r' && start+1<end && *(start+1)=='\n')
250 sprintf(CS new,"=%02X",ch);
257 *new='\0'; /* not included in length, but nice */
262 /*************************************************
263 * Octet-wise string comparison *
264 *************************************************/
268 needle UTF-8 string to search ...
269 haystack ... inside the haystack
270 match_prefix 1 to compare if needle is a prefix of haystack
272 Returns: 0 needle not found in haystack
276 static int eq_octet(const struct String *needle,
277 const struct String *haystack, int match_prefix)
285 h=haystack->character;
289 if (*n&0x80) return 0;
290 if (*h&0x80) return 0;
292 if (*n!=*h) return 0;
298 return (match_prefix ? nl==0 : nl==0 && hl==0);
302 /*************************************************
303 * ASCII case-insensitive string comparison *
304 *************************************************/
308 needle UTF-8 string to search ...
309 haystack ... inside the haystack
310 match_prefix 1 to compare if needle is a prefix of haystack
312 Returns: 0 needle not found in haystack
316 static int eq_asciicase(const struct String *needle,
317 const struct String *haystack, int match_prefix)
326 h=haystack->character;
332 if (nc&0x80) return 0;
333 if (hc&0x80) return 0;
335 /* tolower depends on the locale and only ASCII case must be insensitive */
336 if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
342 return (match_prefix ? nl==0 : nl==0 && hl==0);
346 /*************************************************
347 * Glob pattern search *
348 *************************************************/
352 needle pattern to search ...
353 haystack ... inside the haystack
355 Returns: 0 needle not found in haystack
360 static int eq_glob(const struct String *needle,
361 const struct String *haystack, int ascii_caseless)
363 const uschar *n,*h,*nend,*hend;
367 h=haystack->character;
368 nend=n+needle->length;
369 hend=h+haystack->length;
379 const uschar *npart,*hpart;
381 /* Try to match a non-star part of the needle at the current */
382 /* position in the haystack. */
386 while (npart<nend && *npart!='*') switch (*npart)
390 if (hpart==hend) return 0;
391 /* watch out: Do not match one character, but one UTF8 encoded character */
392 if ((*hpart&0xc0)==0xc0)
395 while (hpart<hend && ((*hpart&0xc0)==0x80)) ++hpart;
405 if (npart==nend) return -1;
410 if (hpart==hend) return 0;
411 /* tolower depends on the locale, but we need ASCII */
415 (*hpart&0x80) || (*npart&0x80) ||
418 ? ((*npart>='A' && *npart<='Z' ? *npart|0x20 : *npart) != (*hpart>='A' && *hpart<='Z' ? *hpart|0x20 : *hpart))
423 /* string match after a star failed, advance and try again */
437 /* at this point, a part was matched successfully */
438 if (may_advance && npart==nend && hpart<hend)
439 /* needle ends, but haystack does not: if there was a star before, advance and try again */
449 return (h==hend ? 1 : may_advance);
453 /*************************************************
454 * ASCII numeric comparison *
455 *************************************************/
459 a first numeric string
460 b second numeric string
461 relop relational operator
463 Returns: 0 not (a relop b)
467 static int eq_asciinumeric(const struct String *a,
468 const struct String *b, enum RelOp relop)
471 const uschar *as,*aend,*bs,*bend;
475 aend=a->character+a->length;
477 bend=b->character+b->length;
479 while (*as>='0' && *as<='9' && as<aend) ++as;
481 while (*bs>='0' && *bs<='9' && bs<bend) ++bs;
484 if (al && bl==0) cmp=-1;
485 else if (al==0 && bl==0) cmp=0;
486 else if (al==0 && bl) cmp=1;
490 if (cmp==0) cmp=memcmp(a->character,b->character,al);
494 case LT: return cmp<0;
495 case LE: return cmp<=0;
496 case EQ: return cmp==0;
497 case GE: return cmp>=0;
498 case GT: return cmp>0;
499 case NE: return cmp!=0;
506 /*************************************************
508 *************************************************/
512 needle UTF-8 pattern or string to search ...
513 haystack ... inside the haystack
517 Returns: 0 needle not found in haystack
519 -1 comparator does not offer matchtype
522 static int compare(struct Sieve *filter, const struct String *needle, const struct String *haystack,
523 enum Comparator co, enum MatchType mt)
527 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
528 (debug_selector & D_filter) != 0)
530 debug_printf("String comparison (match ");
533 case MATCH_IS: debug_printf(":is"); break;
534 case MATCH_CONTAINS: debug_printf(":contains"); break;
535 case MATCH_MATCHES: debug_printf(":matches"); break;
537 debug_printf(", comparison \"");
540 case COMP_OCTET: debug_printf("i;octet"); break;
541 case COMP_EN_ASCII_CASEMAP: debug_printf("en;ascii-casemap"); break;
542 case COMP_ASCII_NUMERIC: debug_printf("i;ascii-numeric"); break;
544 debug_printf("\"):\n");
545 debug_printf(" Search = %s (%d chars)\n", needle->character,needle->length);
546 debug_printf(" Inside = %s (%d chars)\n", haystack->character,haystack->length);
556 if (eq_octet(needle,haystack,0)) r=1;
559 case COMP_EN_ASCII_CASEMAP:
561 if (eq_asciicase(needle,haystack,0)) r=1;
564 case COMP_ASCII_NUMERIC:
566 if (!filter->require_iascii_numeric)
568 filter->errmsg=CUS "missing previous require \"comparator-i;ascii-numeric\";";
571 if (eq_asciinumeric(needle,haystack,EQ)) r=1;
585 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_octet(needle,&h,1)) { r=1; break; }
588 case COMP_EN_ASCII_CASEMAP:
590 for (h=*haystack; h.length; ++h.character,--h.length) if (eq_asciicase(needle,&h,1)) { r=1; break; }
595 filter->errmsg=CUS "comparator does not offer specified matchtype";
607 if ((r=eq_glob(needle,haystack,0))==-1)
609 filter->errmsg=CUS "syntactically invalid pattern";
614 case COMP_EN_ASCII_CASEMAP:
616 if ((r=eq_glob(needle,haystack,1))==-1)
618 filter->errmsg=CUS "syntactically invalid pattern";
625 filter->errmsg=CUS "comparator does not offer specified matchtype";
632 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
633 (debug_selector & D_filter) != 0)
634 debug_printf(" Result %s\n",r?"true":"false");
639 /*************************************************
640 * Check header field syntax *
641 *************************************************/
644 RFC 2822, section 3.6.8 says:
648 ftext = %d33-57 / ; Any character except
649 %d59-126 ; controls, SP, and
652 That forbids 8-bit header fields. This implementation accepts them, since
653 all of Exim is 8-bit clean, so it adds %d128-%d255.
656 header header field to quote for suitable use in Exim expansions
658 Returns: 0 string is not a valid header field
659 1 string is a value header field
662 static int is_header(const struct String *header)
672 if (((unsigned char)*h)<33 || ((unsigned char)*h)==':' || ((unsigned char)*h)==127) return 0;
683 /*************************************************
684 * Quote special characters string *
685 *************************************************/
689 header header field to quote for suitable use in Exim expansions
692 Returns: quoted string
695 static const uschar *quote(const struct String *header)
710 quoted=string_cat(quoted,&size,&ptr,CUS "\\0",2);
717 quoted=string_cat(quoted,&size,&ptr,CUS "\\",1);
721 quoted=string_cat(quoted,&size,&ptr,h,1);
727 quoted=string_cat(quoted,&size,&ptr,CUS "",1);
732 /*************************************************
733 * Add address to list of generated addresses *
734 *************************************************/
737 According to RFC 3028, duplicate delivery to the same address must
738 not happen, so the list is first searched for the address.
741 generated list of generated addresses
742 addr new address to add
743 file address denotes a file
748 static void add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
750 address_item *new_addr;
752 for (new_addr=*generated; new_addr; new_addr=new_addr->next)
754 if (Ustrcmp(new_addr->address,addr)==0 && (file ? testflag(new_addr, af_pfr|af_file) : 1))
756 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
758 debug_printf("Repeated %s `%s' ignored.\n",file ? "fileinto" : "redirect", addr);
764 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
766 debug_printf("%s `%s'\n",file ? "fileinto" : "redirect", addr);
768 new_addr=deliver_make_addr(addr,TRUE);
771 setflag(new_addr, af_pfr|af_file);
774 new_addr->p.errors_address = NULL;
775 new_addr->next = *generated;
776 *generated = new_addr;
780 /*************************************************
781 * Return decoded header field *
782 *************************************************/
786 value returned value of the field
787 header name of the header field
789 Returns: nothing The expanded string is empty
790 in case there is no such header
793 static void expand_header(struct String *value, const struct String *header)
799 value->character=(uschar*)0;
801 t=r=s=expand_string(string_sprintf("$rheader_%s",quote(header)));
808 while (*r==' ' || *r=='\t') ++r;
815 value->character=rfc2047_decode(s,check_rfc2047_length,US"utf-8",'\0',&value->length,&errmsg);
819 /*************************************************
820 * Parse remaining hash comment *
821 *************************************************/
825 Comment up to terminating CRLF
828 filter points to the Sieve filter including its state
834 static int parse_hashcomment(struct Sieve *filter)
840 if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
842 if (*filter->pc=='\n')
855 filter->errmsg=CUS "missing end of comment";
860 /*************************************************
861 * Parse remaining C-style comment *
862 *************************************************/
866 Everything up to star slash
869 filter points to the Sieve filter including its state
875 static int parse_comment(struct Sieve *filter)
880 if (*filter->pc=='*' && *(filter->pc+1)=='/')
887 filter->errmsg=CUS "missing end of comment";
892 /*************************************************
893 * Parse optional white space *
894 *************************************************/
898 Spaces, tabs, CRLFs, hash comments or C-style comments
901 filter points to the Sieve filter including its state
907 static int parse_white(struct Sieve *filter)
911 if (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
913 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
915 else if (*filter->pc=='\n')
925 else if (*filter->pc=='#')
927 if (parse_hashcomment(filter)==-1) return -1;
929 else if (*filter->pc=='/' && *(filter->pc+1)=='*')
931 if (parse_comment(filter)==-1) return -1;
939 /*************************************************
940 * Parse a optional string *
941 *************************************************/
945 quoted-string = DQUOTE *CHAR DQUOTE
946 ;; in general, \ CHAR inside a string maps to CHAR
947 ;; so \" maps to " and \\ maps to \
948 ;; note that newlines and other characters are all allowed
951 multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
952 *(multi-line-literal / multi-line-dotstuff)
954 multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
955 multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
956 ;; A line containing only "." ends the multi-line.
957 ;; Remove a leading '.' if followed by another '.'.
958 string = quoted-string / multi-line
961 filter points to the Sieve filter including its state
962 id specifies identifier to match
966 0 identifier not matched
969 static int parse_string(struct Sieve *filter, struct String *data)
974 data->character=(uschar*)0;
975 if (*filter->pc=='"') /* quoted string */
980 if (*filter->pc=='"') /* end of string */
982 int foo=data->length;
985 data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
988 else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
990 if (*(filter->pc+1)=='0') data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
991 else data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc+1,1);
994 else /* regular character */
996 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1000 filter->errmsg=CUS "missing end of string";
1003 else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
1006 /* skip optional white space followed by hashed comment or CRLF */
1007 while (*filter->pc==' ' || *filter->pc=='\t') ++filter->pc;
1008 if (*filter->pc=='#')
1010 if (parse_hashcomment(filter)==-1) return -1;
1013 else if (*filter->pc=='\r' && *(filter->pc+1)=='\n')
1015 else if (*filter->pc=='\n')
1027 filter->errmsg=CUS "syntax error";
1033 if (*filter->pc=='\r' && *(filter->pc+1)=='\n') /* end of line */
1035 if (*filter->pc=='\n') /* end of line */
1038 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
1046 if (*filter->pc=='.' && *(filter->pc+1)=='\r' && *(filter->pc+2)=='\n') /* end of string */
1048 if (*filter->pc=='.' && *(filter->pc+1)=='\n') /* end of string */
1051 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "",1);
1060 else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
1062 data->character=string_cat(data->character,&dataCapacity,&data->length,CUS ".",1);
1066 else /* regular character */
1068 data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
1072 filter->errmsg=CUS "missing end of multi line string";
1079 /*************************************************
1080 * Parse a specific identifier *
1081 *************************************************/
1085 identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
1088 filter points to the Sieve filter including its state
1089 id specifies identifier to match
1092 0 identifier not matched
1095 static int parse_identifier(struct Sieve *filter, const uschar *id)
1097 size_t idlen=Ustrlen(id);
1099 if (Ustrncmp(filter->pc,id,idlen)==0)
1101 uschar next=filter->pc[idlen];
1103 if ((next>='A' && next<='Z') || (next>='a' && next<='z') || next=='_' || (next>='0' && next<='9')) return 0;
1111 /*************************************************
1113 *************************************************/
1117 number = 1*DIGIT [QUANTIFIER]
1118 QUANTIFIER = "K" / "M" / "G"
1121 filter points to the Sieve filter including its state
1125 -1 no string list found
1128 static int parse_number(struct Sieve *filter, unsigned long *data)
1132 if (*filter->pc>='0' && *filter->pc<='9')
1137 d=Ustrtoul(filter->pc,&e,10);
1140 filter->errmsg=CUstrerror(ERANGE);
1145 if (*filter->pc=='K') { u=1024; ++filter->pc; }
1146 else if (*filter->pc=='M') { u=1024*1024; ++filter->pc; }
1147 else if (*filter->pc=='G') { u=1024*1024*1024; ++filter->pc; }
1148 if (d>(ULONG_MAX/u))
1150 filter->errmsg=CUstrerror(ERANGE);
1159 filter->errmsg=CUS "missing number";
1165 /*************************************************
1166 * Parse a string list *
1167 *************************************************/
1171 string-list = "[" string *("," string) "]" / string
1174 filter points to the Sieve filter including its state
1175 data returns string list
1178 -1 no string list found
1181 static int parse_stringlist(struct Sieve *filter, struct String **data)
1183 const uschar *orig=filter->pc;
1186 struct String *d=(struct String*)0;
1189 if (*filter->pc=='[') /* string list */
1194 if (parse_white(filter)==-1) goto error;
1195 if ((dataLength+1)>=dataCapacity) /* increase buffer */
1198 int newCapacity; /* Don't amalgamate with next line; some compilers grumble */
1199 newCapacity=dataCapacity?(dataCapacity*=2):(dataCapacity=4);
1200 if ((new=(struct String*)store_get(sizeof(struct String)*newCapacity))==(struct String*)0)
1202 filter->errmsg=CUstrerror(errno);
1205 if (d) memcpy(new,d,sizeof(struct String)*dataLength);
1207 dataCapacity=newCapacity;
1209 m=parse_string(filter,&d[dataLength]);
1212 if (dataLength==0) break;
1215 filter->errmsg=CUS "missing string";
1219 else if (m==-1) goto error;
1221 if (parse_white(filter)==-1) goto error;
1222 if (*filter->pc==',') ++filter->pc;
1225 if (*filter->pc==']')
1227 d[dataLength].character=(uschar*)0;
1228 d[dataLength].length=-1;
1235 filter->errmsg=CUS "missing closing bracket";
1239 else /* single string */
1241 if ((d=store_get(sizeof(struct String)*2))==(struct String*)0)
1245 m=parse_string(filter,&d[0]);
1257 d[1].character=(uschar*)0;
1264 filter->errmsg=CUS "missing string list";
1269 /*************************************************
1270 * Parse an optional address part specifier *
1271 *************************************************/
1275 address-part = ":localpart" / ":domain" / ":all"
1276 address-part =/ ":user" / ":detail"
1279 filter points to the Sieve filter including its state
1280 a returns address part specified
1283 0 no comparator found
1287 static int parse_addresspart(struct Sieve *filter, enum AddressPart *a)
1290 if (parse_identifier(filter,CUS ":user")==1)
1292 if (!filter->require_subaddress)
1294 filter->errmsg=CUS "missing previous require \"subaddress\";";
1300 else if (parse_identifier(filter,CUS ":detail")==1)
1302 if (!filter->require_subaddress)
1304 filter->errmsg=CUS "missing previous require \"subaddress\";";
1312 if (parse_identifier(filter,CUS ":localpart")==1)
1314 *a=ADDRPART_LOCALPART;
1317 else if (parse_identifier(filter,CUS ":domain")==1)
1322 else if (parse_identifier(filter,CUS ":all")==1)
1331 /*************************************************
1332 * Parse an optional comparator *
1333 *************************************************/
1337 comparator = ":comparator" <comparator-name: string>
1340 filter points to the Sieve filter including its state
1341 c returns comparator
1344 0 no comparator found
1345 -1 incomplete comparator found
1348 static int parse_comparator(struct Sieve *filter, enum Comparator *c)
1350 struct String comparator_name;
1352 if (parse_identifier(filter,CUS ":comparator")==0) return 0;
1353 if (parse_white(filter)==-1) return -1;
1354 switch (parse_string(filter,&comparator_name))
1359 filter->errmsg=CUS "missing comparator";
1366 if (eq_asciicase(&comparator_name,&str_ioctet,0))
1371 else if (eq_asciicase(&comparator_name,&str_iascii_casemap,0))
1373 *c=COMP_EN_ASCII_CASEMAP;
1376 else if (eq_asciicase(&comparator_name,&str_enascii_casemap,0))
1378 *c=COMP_EN_ASCII_CASEMAP;
1381 else if (eq_asciicase(&comparator_name,&str_iascii_numeric,0))
1383 *c=COMP_ASCII_NUMERIC;
1388 filter->errmsg=CUS "invalid comparator";
1397 /*************************************************
1398 * Parse an optional match type *
1399 *************************************************/
1403 match-type = ":is" / ":contains" / ":matches"
1406 filter points to the Sieve filter including its state
1407 m returns match type
1410 0 no match type found
1413 static int parse_matchtype(struct Sieve *filter, enum MatchType *m)
1415 if (parse_identifier(filter,CUS ":is")==1)
1420 else if (parse_identifier(filter,CUS ":contains")==1)
1425 else if (parse_identifier(filter,CUS ":matches")==1)
1434 /*************************************************
1435 * Parse and interpret an optional test list *
1436 *************************************************/
1440 test-list = "(" test *("," test) ")"
1443 filter points to the Sieve filter including its state
1444 n total number of tests
1445 true number of passed tests
1446 exec Execute parsed statements
1449 0 no test list found
1450 -1 syntax or execution error
1453 static int parse_testlist(struct Sieve *filter, int *n, int *true, int exec)
1455 if (parse_white(filter)==-1) return -1;
1456 if (*filter->pc=='(')
1465 switch (parse_test(filter,&cond,exec))
1468 case 0: filter->errmsg=CUS "missing test"; return -1;
1469 default: ++*n; if (cond) ++*true; break;
1471 if (parse_white(filter)==-1) return -1;
1472 if (*filter->pc==',') ++filter->pc;
1475 if (*filter->pc==')')
1482 filter->errmsg=CUS "missing closing paren";
1490 /*************************************************
1491 * Parse and interpret an optional test *
1492 *************************************************/
1496 filter points to the Sieve filter including its state
1497 cond returned condition status
1498 exec Execute parsed statements
1502 -1 syntax or execution error
1505 static int parse_test(struct Sieve *filter, int *cond, int exec)
1507 if (parse_white(filter)==-1) return -1;
1508 if (parse_identifier(filter,CUS "address"))
1511 address-test = "address" { [address-part] [comparator] [match-type] }
1512 <header-list: string-list> <key-list: string-list>
1514 header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
1517 enum AddressPart addressPart=ADDRPART_ALL;
1518 enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
1519 enum MatchType matchType=MATCH_IS;
1520 struct String *hdr,*h,*key,*k;
1526 if (parse_white(filter)==-1) return -1;
1527 if ((m=parse_addresspart(filter,&addressPart))!=0)
1529 if (m==-1) return -1;
1532 filter->errmsg=CUS "address part already specified";
1537 else if ((m=parse_comparator(filter,&comparator))!=0)
1539 if (m==-1) return -1;
1542 filter->errmsg=CUS "comparator already specified";
1547 else if ((m=parse_matchtype(filter,&matchType))!=0)
1549 if (m==-1) return -1;
1552 filter->errmsg=CUS "match type already specified";
1559 if (parse_white(filter)==-1) return -1;
1560 if ((m=parse_stringlist(filter,&hdr))!=1)
1562 if (m==0) filter->errmsg=CUS "header string list expected";
1565 if (parse_white(filter)==-1) return -1;
1566 if ((m=parse_stringlist(filter,&key))!=1)
1568 if (m==0) filter->errmsg=CUS "key string list expected";
1572 for (h=hdr; h->length!=-1 && !*cond; ++h)
1574 uschar *header_value=(uschar*)0,*extracted_addr,*end_addr;
1578 !eq_asciicase(h,&str_from,0)
1579 && !eq_asciicase(h,&str_to,0)
1580 && !eq_asciicase(h,&str_cc,0)
1581 && !eq_asciicase(h,&str_bcc,0)
1582 && !eq_asciicase(h,&str_sender,0)
1583 && !eq_asciicase(h,&str_resent_from,0)
1584 && !eq_asciicase(h,&str_resent_to,0)
1587 filter->errmsg=CUS "invalid header field";
1592 /* We are only interested in addresses below, so no MIME decoding */
1593 header_value=expand_string(string_sprintf("$rheader_%s",quote(h)));
1594 if (header_value == NULL)
1596 filter->errmsg=CUS "header string expansion failed";
1599 parse_allow_group = TRUE;
1600 while (*header_value && !*cond)
1603 int start, end, domain;
1607 end_addr = parse_find_address_end(header_value, FALSE);
1608 saveend = *end_addr;
1610 extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
1612 if (extracted_addr) switch (addressPart)
1614 case ADDRPART_ALL: part=extracted_addr; break;
1618 case ADDRPART_LOCALPART: part=extracted_addr; part[domain-1]='\0'; break;
1619 case ADDRPART_DOMAIN: part=extracted_addr+domain; break;
1621 case ADDRPART_DETAIL: part=NULL; break;
1625 *end_addr = saveend;
1628 for (k=key; k->length!=-1; ++k)
1630 struct String partStr;
1632 partStr.character=part;
1633 partStr.length=Ustrlen(part);
1636 *cond=compare(filter,k,&partStr,comparator,matchType);
1637 if (*cond==-1) return -1;
1642 if (saveend == 0) break;
1643 header_value = end_addr + 1;
1649 else if (parse_identifier(filter,CUS "allof"))
1652 allof-test = "allof" <tests: test-list>
1657 switch (parse_testlist(filter,&n,&true,exec))
1660 case 0: filter->errmsg=CUS "missing test list"; return -1;
1661 default: *cond=(n==true); return 1;
1664 else if (parse_identifier(filter,CUS "anyof"))
1667 anyof-test = "anyof" <tests: test-list>
1672 switch (parse_testlist(filter,&n,&true,exec))
1675 case 0: filter->errmsg=CUS "missing test list"; return -1;
1676 default: *cond=(true>0); return 1;
1679 else if (parse_identifier(filter,CUS "exists"))
1682 exists-test = "exists" <header-names: string-list>
1685 struct String *hdr,*h;
1688 if (parse_white(filter)==-1) return -1;
1689 if ((m=parse_stringlist(filter,&hdr))!=1)
1691 if (m==0) filter->errmsg=CUS "header string list expected";
1697 for (h=hdr; h->length!=-1 && *cond; ++h)
1701 header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
1702 if (header_def == NULL)
1704 filter->errmsg=CUS "header string expansion failed";
1707 if (Ustrcmp(header_def,"false")==0) *cond=0;
1712 else if (parse_identifier(filter,CUS "false"))
1715 false-test = "false"
1721 else if (parse_identifier(filter,CUS "header"))
1724 header-test = "header" { [comparator] [match-type] }
1725 <header-names: string-list> <key-list: string-list>
1728 enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
1729 enum MatchType matchType=MATCH_IS;
1730 struct String *hdr,*h,*key,*k;
1736 if (parse_white(filter)==-1) return -1;
1737 if ((m=parse_comparator(filter,&comparator))!=0)
1739 if (m==-1) return -1;
1742 filter->errmsg=CUS "comparator already specified";
1747 else if ((m=parse_matchtype(filter,&matchType))!=0)
1749 if (m==-1) return -1;
1752 filter->errmsg=CUS "match type already specified";
1759 if (parse_white(filter)==-1) return -1;
1760 if ((m=parse_stringlist(filter,&hdr))!=1)
1762 if (m==0) filter->errmsg=CUS "header string list expected";
1765 if (parse_white(filter)==-1) return -1;
1766 if ((m=parse_stringlist(filter,&key))!=1)
1768 if (m==0) filter->errmsg=CUS "key string list expected";
1772 for (h=hdr; h->length!=-1 && !*cond; ++h)
1776 filter->errmsg=CUS "invalid header field";
1781 struct String header_value;
1784 expand_header(&header_value,h);
1785 header_def=expand_string(string_sprintf("${if def:header_%s {true}{false}}",quote(h)));
1786 if (header_value.character == NULL || header_def == NULL)
1788 filter->errmsg=CUS "header string expansion failed";
1791 for (k=key; k->length!=-1; ++k)
1793 if (Ustrcmp(header_def,"true")==0)
1795 *cond=compare(filter,k,&header_value,comparator,matchType);
1796 if (*cond==-1) return -1;
1804 else if (parse_identifier(filter,CUS "not"))
1806 if (parse_white(filter)==-1) return -1;
1807 switch (parse_test(filter,cond,exec))
1810 case 0: filter->errmsg=CUS "missing test"; return -1;
1811 default: *cond=!*cond; return 1;
1814 else if (parse_identifier(filter,CUS "size"))
1817 relop = ":over" / ":under"
1818 size-test = "size" relop <limit: number>
1821 unsigned long limit;
1824 if (parse_white(filter)==-1) return -1;
1825 if (parse_identifier(filter,CUS ":over")) overNotUnder=1;
1826 else if (parse_identifier(filter,CUS ":under")) overNotUnder=0;
1829 filter->errmsg=CUS "missing :over or :under";
1832 if (parse_white(filter)==-1) return -1;
1833 if (parse_number(filter,&limit)==-1) return -1;
1834 *cond=(overNotUnder ? (message_size>limit) : (message_size<limit));
1837 else if (parse_identifier(filter,CUS "true"))
1842 else if (parse_identifier(filter,CUS "envelope"))
1845 envelope-test = "envelope" { [comparator] [address-part] [match-type] }
1846 <envelope-part: string-list> <key-list: string-list>
1848 envelope-part is case insensitive "from" or "to"
1849 #ifdef ENVELOPE_AUTH
1850 envelope-part =/ "auth"
1854 enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
1855 enum AddressPart addressPart=ADDRPART_ALL;
1856 enum MatchType matchType=MATCH_IS;
1857 struct String *env,*e,*key,*k;
1861 if (!filter->require_envelope)
1863 filter->errmsg=CUS "missing previous require \"envelope\";";
1868 if (parse_white(filter)==-1) return -1;
1869 if ((m=parse_comparator(filter,&comparator))!=0)
1871 if (m==-1) return -1;
1874 filter->errmsg=CUS "comparator already specified";
1879 else if ((m=parse_addresspart(filter,&addressPart))!=0)
1881 if (m==-1) return -1;
1884 filter->errmsg=CUS "address part already specified";
1889 else if ((m=parse_matchtype(filter,&matchType))!=0)
1891 if (m==-1) return -1;
1894 filter->errmsg=CUS "match type already specified";
1901 if (parse_white(filter)==-1) return -1;
1902 if ((m=parse_stringlist(filter,&env))!=1)
1904 if (m==0) filter->errmsg=CUS "envelope string list expected";
1907 if (parse_white(filter)==-1) return -1;
1908 if ((m=parse_stringlist(filter,&key))!=1)
1910 if (m==0) filter->errmsg=CUS "key string list expected";
1914 for (e=env; e->length!=-1 && !*cond; ++e)
1916 const uschar *envelopeExpr=CUS 0;
1917 uschar *envelope=US 0;
1919 if (eq_asciicase(e,&str_from,0))
1921 switch (addressPart)
1923 case ADDRPART_ALL: envelopeExpr=CUS "$sender_address"; break;
1927 case ADDRPART_LOCALPART: envelopeExpr=CUS "${local_part:$sender_address}"; break;
1928 case ADDRPART_DOMAIN: envelopeExpr=CUS "${domain:$sender_address}"; break;
1930 case ADDRPART_DETAIL: envelopeExpr=CUS 0; break;
1934 else if (eq_asciicase(e,&str_to,0))
1936 switch (addressPart)
1938 case ADDRPART_ALL: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
1940 case ADDRPART_USER: envelopeExpr=filter->useraddress; break;
1941 case ADDRPART_DETAIL: envelopeExpr=filter->subaddress; break;
1943 case ADDRPART_LOCALPART: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix"; break;
1944 case ADDRPART_DOMAIN: envelopeExpr=CUS "$domain"; break;
1947 #ifdef ENVELOPE_AUTH
1948 else if (eq_asciicase(e,&str_auth,0))
1950 switch (addressPart)
1952 case ADDRPART_ALL: envelopeExpr=CUS "$authenticated_sender"; break;
1956 case ADDRPART_LOCALPART: envelopeExpr=CUS "${local_part:$authenticated_sender}"; break;
1957 case ADDRPART_DOMAIN: envelopeExpr=CUS "${domain:$authenticated_sender}"; break;
1959 case ADDRPART_DETAIL: envelopeExpr=CUS 0; break;
1966 filter->errmsg=CUS "invalid envelope string";
1969 if (exec && envelopeExpr)
1971 if ((envelope=expand_string(US envelopeExpr)) == NULL)
1973 filter->errmsg=CUS "header string expansion failed";
1976 for (k=key; k->length!=-1; ++k)
1978 struct String envelopeStr;
1980 envelopeStr.character=envelope;
1981 envelopeStr.length=Ustrlen(envelope);
1982 *cond=compare(filter,k,&envelopeStr,comparator,matchType);
1983 if (*cond==-1) return -1;
1994 /*************************************************
1995 * Parse and interpret an optional block *
1996 *************************************************/
2000 filter points to the Sieve filter including its state
2001 exec Execute parsed statements
2002 generated where to hang newly-generated addresses
2004 Returns: 2 success by stop
2006 0 no block command found
2007 -1 syntax or execution error
2010 static int parse_block(struct Sieve *filter, int exec,
2011 address_item **generated)
2015 if (parse_white(filter)==-1) return -1;
2016 if (*filter->pc=='{')
2019 if ((r=parse_commands(filter,exec,generated))==-1 || r==2) return r;
2020 if (*filter->pc=='}')
2027 filter->errmsg=CUS "expecting command or closing brace";
2035 /*************************************************
2036 * Match a semicolon *
2037 *************************************************/
2041 filter points to the Sieve filter including its state
2047 static int parse_semicolon(struct Sieve *filter)
2049 if (parse_white(filter)==-1) return -1;
2050 if (*filter->pc==';')
2057 filter->errmsg=CUS "missing semicolon";
2063 /*************************************************
2064 * Parse and interpret a Sieve command *
2065 *************************************************/
2069 filter points to the Sieve filter including its state
2070 exec Execute parsed statements
2071 generated where to hang newly-generated addresses
2073 Returns: 2 success by stop
2075 -1 syntax or execution error
2077 static int parse_commands(struct Sieve *filter, int exec,
2078 address_item **generated)
2082 if (parse_white(filter)==-1) return -1;
2083 if (parse_identifier(filter,CUS "if"))
2086 if-command = "if" test block *( "elsif" test block ) [ else block ]
2089 int cond,m,unsuccessful;
2092 if (parse_white(filter)==-1) return -1;
2093 if ((m=parse_test(filter,&cond,exec))==-1) return -1;
2096 filter->errmsg=CUS "missing test";
2099 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
2100 (debug_selector & D_filter) != 0)
2102 if (exec) debug_printf("if %s\n",cond?"true":"false");
2104 m=parse_block(filter,exec ? cond : 0, generated);
2105 if (m==-1 || m==2) return m;
2108 filter->errmsg=CUS "missing block";
2111 unsuccessful = !cond;
2112 for (;;) /* elsif test block */
2114 if (parse_white(filter)==-1) return -1;
2115 if (parse_identifier(filter,CUS "elsif"))
2117 if (parse_white(filter)==-1) return -1;
2118 m=parse_test(filter,&cond,exec && unsuccessful);
2119 if (m==-1 || m==2) return m;
2122 filter->errmsg=CUS "missing test";
2125 if ((filter_test != FTEST_NONE && debug_selector != 0) ||
2126 (debug_selector & D_filter) != 0)
2128 if (exec) debug_printf("elsif %s\n",cond?"true":"false");
2130 m=parse_block(filter,exec && unsuccessful ? cond : 0, generated);
2131 if (m==-1 || m==2) return m;
2134 filter->errmsg=CUS "missing block";
2137 if (exec && unsuccessful && cond) unsuccessful = 0;
2142 if (parse_white(filter)==-1) return -1;
2143 if (parse_identifier(filter,CUS "else"))
2145 m=parse_block(filter,exec && unsuccessful, generated);
2146 if (m==-1 || m==2) return m;
2149 filter->errmsg=CUS "missing block";
2154 else if (parse_identifier(filter,CUS "stop"))
2157 stop-command = "stop" { stop-options } ";"
2161 if (parse_semicolon(filter)==-1) return -1;
2164 filter->pc+=Ustrlen(filter->pc);
2168 else if (parse_identifier(filter,CUS "keep"))
2171 keep-command = "keep" { keep-options } ";"
2175 if (parse_semicolon(filter)==-1) return -1;
2178 add_addr(generated,US"inbox",1,0,0,0);
2182 else if (parse_identifier(filter,CUS "discard"))
2185 discard-command = "discard" { discard-options } ";"
2189 if (parse_semicolon(filter)==-1) return -1;
2190 if (exec) filter->keep=0;
2192 else if (parse_identifier(filter,CUS "redirect"))
2195 redirect-command = "redirect" redirect-options "string" ";"
2197 redirect-options =) ":copy"
2200 struct String recipient;
2206 if (parse_white(filter)==-1) return -1;
2207 if (parse_identifier(filter,CUS ":copy")==1)
2209 if (!filter->require_copy)
2211 filter->errmsg=CUS "missing previous require \"copy\";";
2218 if (parse_white(filter)==-1) return -1;
2219 if ((m=parse_string(filter,&recipient))!=1)
2221 if (m==0) filter->errmsg=CUS "missing redirect recipient string";
2224 if (strchr(CCS recipient.character,'@')==(char*)0)
2226 filter->errmsg=CUS "unqualified recipient address";
2231 add_addr(generated,recipient.character,0,0,0,0);
2232 if (!copy) filter->keep = 0;
2234 if (parse_semicolon(filter)==-1) return -1;
2236 else if (parse_identifier(filter,CUS "fileinto"))
2239 fileinto-command = "fileinto" { fileinto-options } string ";"
2241 fileinto-options =) [ ":copy" ]
2244 struct String folder;
2247 unsigned long maxage, maxmessages, maxstorage;
2250 maxage = maxmessages = maxstorage = 0;
2251 if (!filter->require_fileinto)
2253 filter->errmsg=CUS "missing previous require \"fileinto\";";
2258 if (parse_white(filter)==-1) return -1;
2259 if (parse_identifier(filter,CUS ":copy")==1)
2261 if (!filter->require_copy)
2263 filter->errmsg=CUS "missing previous require \"copy\";";
2270 if (parse_white(filter)==-1) return -1;
2271 if ((m=parse_string(filter,&folder))!=1)
2273 if (m==0) filter->errmsg=CUS "missing fileinto folder string";
2276 m=0; s=folder.character;
2277 if (folder.length==0) m=1;
2278 if (Ustrcmp(s,"..")==0 || Ustrncmp(s,"../",3)==0) m=1;
2281 if (Ustrcmp(s,"/..")==0 || Ustrncmp(s,"/../",4)==0) { m=1; break; }
2286 filter->errmsg=CUS "invalid folder";
2291 add_addr(generated, folder.character, 1, maxage, maxmessages, maxstorage);
2292 if (!copy) filter->keep = 0;
2294 if (parse_semicolon(filter)==-1) return -1;
2297 else if (parse_identifier(filter,CUS "notify"))
2300 notify-command = "notify" { notify-options } ";"
2301 notify-options = [":method" string]
2306 struct String method;
2307 struct String message;
2309 if (!filter->require_notify)
2311 filter->errmsg=CUS "missing previous require \"notify\";";
2314 method.character=(uschar*)0;
2316 message.character=(uschar*)0;
2320 if (parse_white(filter)==-1) return -1;
2321 if (parse_identifier(filter,CUS ":method")==1)
2323 if (parse_white(filter)==-1) return -1;
2324 if ((m=parse_string(filter,&method))!=1)
2326 if (m==0) filter->errmsg=CUS "method string expected";
2330 else if (parse_identifier(filter,CUS ":message")==1)
2332 if (parse_white(filter)==-1) return -1;
2333 if ((m=parse_string(filter,&message))!=1)
2335 if (m==0) filter->errmsg=CUS "message string expected";
2341 if (parse_semicolon(filter)==-1) return -1;
2345 else if (parse_identifier(filter,CUS "vacation"))
2348 vacation-command = "vacation" { vacation-options } <reason: string> ";"
2349 vacation-options = [":days" number]
2352 [":addresses" string-list]
2359 struct String subject;
2361 struct String *addresses;
2363 string_item *aliases;
2364 struct String handle;
2365 struct String reason;
2367 if (!filter->require_vacation)
2369 filter->errmsg=CUS "missing previous require \"vacation\";";
2374 if (filter->vacation_ran)
2376 filter->errmsg=CUS "trying to execute vacation more than once";
2379 filter->vacation_ran=1;
2381 days=VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
2382 subject.character=(uschar*)0;
2384 from.character=(uschar*)0;
2386 addresses=(struct String*)0;
2389 handle.character=(uschar*)0;
2393 if (parse_white(filter)==-1) return -1;
2394 if (parse_identifier(filter,CUS ":days")==1)
2396 if (parse_white(filter)==-1) return -1;
2397 if (parse_number(filter,&days)==-1) return -1;
2398 if (days<VACATION_MIN_DAYS) days=VACATION_MIN_DAYS;
2399 else if (days>VACATION_MAX_DAYS) days=VACATION_MAX_DAYS;
2401 else if (parse_identifier(filter,CUS ":subject")==1)
2403 if (parse_white(filter)==-1) return -1;
2404 if ((m=parse_string(filter,&subject))!=1)
2406 if (m==0) filter->errmsg=CUS "subject string expected";
2410 else if (parse_identifier(filter,CUS ":from")==1)
2412 int start, end, domain;
2415 if (parse_white(filter)==-1) return -1;
2416 if ((m=parse_string(filter,&from))!=1)
2418 if (m==0) filter->errmsg=CUS "from string expected";
2423 ss = parse_extract_address(from.character, &error, &start, &end, &domain,
2427 filter->errmsg=string_sprintf("malformed address \"%s\" in "
2428 "Sieve filter: %s", from.character, error);
2434 filter->errmsg=CUS "empty :from address in Sieve filter";
2438 else if (parse_identifier(filter,CUS ":addresses")==1)
2442 if (parse_white(filter)==-1) return -1;
2443 if ((m=parse_stringlist(filter,&addresses))!=1)
2445 if (m==0) filter->errmsg=CUS "addresses string list expected";
2448 for (a=addresses; a->length!=-1; ++a)
2452 new=store_get(sizeof(string_item));
2453 new->text=store_get(a->length+1);
2454 if (a->length) memcpy(new->text,a->character,a->length);
2455 new->text[a->length]='\0';
2460 else if (parse_identifier(filter,CUS ":mime")==1)
2462 else if (parse_identifier(filter,CUS ":handle")==1)
2464 if (parse_white(filter)==-1) return -1;
2465 if ((m=parse_string(filter,&from))!=1)
2467 if (m==0) filter->errmsg=CUS "handle string expected";
2473 if (parse_white(filter)==-1) return -1;
2474 if ((m=parse_string(filter,&reason))!=1)
2476 if (m==0) filter->errmsg=CUS "missing reason string";
2483 for (s=reason.character,end=reason.character+reason.length; s<end && (*s&0x80)==0; ++s);
2486 filter->errmsg=CUS "MIME reason string contains 8bit text";
2490 if (parse_semicolon(filter)==-1) return -1;
2497 int buffer_capacity;
2501 uschar hexdigest[33];
2505 if (filter_personal(aliases,TRUE))
2507 if (filter_test == FTEST_NONE)
2509 /* ensure oncelog directory exists; failure will be detected later */
2511 (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
2513 /* build oncelog filename */
2515 key.character=(uschar*)0;
2518 if (handle.length==-1)
2520 if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
2521 if (from.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,from.character,from.length);
2522 key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
2523 key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
2528 md5_end(&base, key.character, key.length, digest);
2529 for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
2530 if (filter_test != FTEST_NONE)
2532 debug_printf("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
2536 capacity=Ustrlen(filter->vacation_directory);
2538 once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
2539 once=string_cat(once,&capacity,&start,hexdigest,33);
2542 /* process subject */
2544 if (subject.length==-1)
2546 expand_header(&subject,&str_subject);
2549 subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
2550 subject.length=start;
2553 /* add address to list of generated addresses */
2555 addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
2556 setflag(addr, af_pfr);
2557 setflag(addr, af_ignore_error);
2558 addr->next = *generated;
2560 addr->reply = store_get(sizeof(reply_item));
2561 memset(addr->reply,0,sizeof(reply_item)); /* XXX */
2562 addr->reply->to = string_copy(sender_address);
2563 if (from.length==-1)
2564 addr->reply->from = expand_string(US"$local_part@$domain");
2566 addr->reply->from = from.character;
2567 /* Allocation is larger than neccessary, but enough even for split MIME words */
2568 buffer_capacity=16+4*subject.length;
2569 buffer=store_get(buffer_capacity);
2570 addr->reply->subject=parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity);
2571 addr->reply->oncelog=once;
2572 addr->reply->once_repeat=days*86400;
2574 /* build body and MIME headers */
2578 uschar *mime_body,*reason_end;
2580 static const uschar nlnl[]="\r\n\r\n";
2582 static const uschar nlnl[]="\n\n";
2587 mime_body=reason.character,reason_end=reason.character+reason.length;
2588 mime_body<(reason_end-sizeof(nlnl)-1) && memcmp(mime_body,nlnl,sizeof(nlnl)-1);
2593 addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
2594 addr->reply->headers[start] = '\0';
2597 if (mime_body+(sizeof(nlnl)-1)<reason_end) mime_body+=sizeof(nlnl)-1;
2598 else mime_body=reason_end-1;
2599 addr->reply->text = string_cat(NULL,&capacity,&start,mime_body,reason_end-mime_body);
2600 addr->reply->text[start] = '\0';
2607 start = reason.length;
2608 addr->reply->headers = US"MIME-Version: 1.0\n"
2609 "Content-Type: text/plain;\n"
2610 "\tcharset=\"utf-8\"\n"
2611 "Content-Transfer-Encoding: quoted-printable";
2612 addr->reply->text = quoted_printable_encode(&reason,&qp)->character;
2616 else if (filter_test != FTEST_NONE)
2618 debug_printf("Sieve: mail was not personal, vacation would ignore it\n");
2629 /*************************************************
2630 * Parse and interpret a sieve filter *
2631 *************************************************/
2635 filter points to the Sieve filter including its state
2636 exec Execute parsed statements
2637 generated where to hang newly-generated addresses
2640 -1 syntax or execution error
2643 static int parse_start(struct Sieve *filter, int exec,
2644 address_item **generated)
2646 filter->pc=filter->filter;
2649 filter->require_envelope=0;
2650 filter->require_fileinto=0;
2651 #ifdef ENVELOPE_AUTH
2652 filter->require_envelope_auth=0;
2655 filter->require_notify=0;
2658 filter->require_subaddress=0;
2661 filter->require_vacation=0;
2662 filter->vacation_ran=0;
2664 filter->require_copy=0;
2665 filter->require_iascii_numeric=0;
2667 if (parse_white(filter)==-1) return -1;
2669 if (exec && filter->vacation_directory != NULL && filter_test == FTEST_NONE)
2672 struct dirent *oncelog;
2673 struct stat properties;
2676 /* clean up old vacation log databases */
2678 oncelogdir=opendir(CS filter->vacation_directory);
2680 if (oncelogdir ==(DIR*)0 && errno != ENOENT)
2682 filter->errmsg=CUS "unable to open vacation directory";
2686 if (oncelogdir != NULL)
2690 while ((oncelog=readdir(oncelogdir))!=(struct dirent*)0)
2692 if (strlen(oncelog->d_name)==32)
2694 uschar *s=string_sprintf("%s/%s",filter->vacation_directory,oncelog->d_name);
2695 if (Ustat(s,&properties)==0 && (properties.st_mtime+VACATION_MAX_DAYS*86400)<now)
2699 closedir(oncelogdir);
2703 while (parse_identifier(filter,CUS "require"))
2706 require-command = "require" <capabilities: string-list>
2709 struct String *cap,*check;
2712 if (parse_white(filter)==-1) return -1;
2713 if ((m=parse_stringlist(filter,&cap))!=1)
2715 if (m==0) filter->errmsg=CUS "capability string list expected";
2718 for (check=cap; check->character; ++check)
2720 if (eq_octet(check,&str_envelope,0)) filter->require_envelope=1;
2721 else if (eq_octet(check,&str_fileinto,0)) filter->require_fileinto=1;
2722 #ifdef ENVELOPE_AUTH
2723 else if (eq_octet(check,&str_envelope_auth,0)) filter->require_envelope_auth=1;
2726 else if (eq_octet(check,&str_notify,0)) filter->require_notify=1;
2729 else if (eq_octet(check,&str_subaddress,0)) filter->require_subaddress=1;
2732 else if (eq_octet(check,&str_vacation,0))
2734 if (filter_test == FTEST_NONE && filter->vacation_directory == NULL)
2736 filter->errmsg=CUS "vacation disabled";
2739 filter->require_vacation=1;
2742 else if (eq_octet(check,&str_copy,0)) filter->require_copy=1;
2743 else if (eq_octet(check,&str_comparator_ioctet,0)) ;
2744 else if (eq_octet(check,&str_comparator_iascii_casemap,0)) ;
2745 else if (eq_octet(check,&str_comparator_enascii_casemap,0)) ;
2746 else if (eq_octet(check,&str_comparator_iascii_numeric,0)) filter->require_iascii_numeric=1;
2749 filter->errmsg=CUS "unknown capability";
2753 if (parse_semicolon(filter)==-1) return -1;
2755 if (parse_commands(filter,exec,generated)==-1) return -1;
2758 filter->errmsg=CUS "syntax error";
2765 /*************************************************
2766 * Interpret a sieve filter file *
2767 *************************************************/
2771 filter points to the entire file, read into store as a single string
2772 options controls whether various special things are allowed, and requests
2773 special actions (not currently used)
2774 sieve_vacation_directory where to store vacation "once" files
2775 useraddress string expression for :user part of address
2776 subaddress string expression for :subaddress part of address
2777 generated where to hang newly-generated addresses
2778 error where to pass back an error text
2780 Returns: FF_DELIVERED success, a significant action was taken
2781 FF_NOTDELIVERED success, no significant action
2782 FF_DEFER defer requested
2783 FF_FAIL fail requested
2784 FF_FREEZE freeze requested
2785 FF_ERROR there was a problem
2789 sieve_interpret(uschar *filter, int options, uschar *vacation_directory,
2790 uschar *useraddress, uschar *subaddress, address_item **generated, uschar **error)
2796 options = options; /* Keep picky compilers happy */
2799 DEBUG(D_route) debug_printf("Sieve: start of processing\n");
2800 sieve.filter=filter;
2802 if (vacation_directory == NULL)
2803 sieve.vacation_directory = NULL;
2806 sieve.vacation_directory=expand_string(vacation_directory);
2807 if (sieve.vacation_directory == NULL)
2809 *error = string_sprintf("failed to expand \"%s\" "
2810 "(sieve_vacation_directory): %s", vacation_directory,
2811 expand_string_message);
2816 sieve.useraddress = useraddress == NULL ? CUS "$local_part_prefix$local_part$local_part_suffix" : useraddress;
2817 sieve.subaddress = subaddress;
2819 #ifdef COMPILE_SYNTAX_CHECKER
2820 if (parse_start(&sieve,0,generated)==1)
2822 if (parse_start(&sieve,1,generated)==1)
2827 add_addr(generated,US"inbox",1,0,0,0);
2828 msg = string_sprintf("Implicit keep");
2833 msg = string_sprintf("No implicit keep");
2839 msg = string_sprintf("Sieve error: %s in line %d",sieve.errmsg,sieve.line);
2840 #ifdef COMPILE_SYNTAX_CHECKER
2844 add_addr(generated,US"inbox",1,0,0,0);
2849 #ifndef COMPILE_SYNTAX_CHECKER
2850 if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
2851 else debug_printf("%s\n", msg);
2854 DEBUG(D_route) debug_printf("Sieve: end of processing\n");