-/* $Cambridge: exim/src/src/sieve.c,v 1.9 2005/04/06 14:40:24 ph10 Exp $ */
+/* $Cambridge: exim/src/src/sieve.c,v 1.13 2005/08/30 10:55:52 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
#undef RFC_EOL
/* Define this for development of the subaddress Sieve extension. */
-/* The code is currently broken. */
-#undef SUBADDRESS
+#define SUBADDRESS
/* Define this for the vacation Sieve extension. */
#define VACATION
int require_iascii_numeric;
};
-enum Comparator { COMP_OCTET, COMP_ASCII_CASEMAP, COMP_ASCII_NUMERIC };
+enum Comparator { COMP_OCTET, COMP_EN_ASCII_CASEMAP, COMP_ASCII_NUMERIC };
enum MatchType { MATCH_IS, MATCH_CONTAINS, MATCH_MATCHES };
#ifdef SUBADDRESS
enum AddressPart { ADDRPART_USER, ADDRPART_DETAIL, ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
static const struct String str_copy={ str_copy_c, 4 };
static uschar str_iascii_casemap_c[]="i;ascii-casemap";
static const struct String str_iascii_casemap={ str_iascii_casemap_c, 15 };
+static uschar str_enascii_casemap_c[]="en;ascii-casemap";
+static const struct String str_enascii_casemap={ str_enascii_casemap_c, 16 };
static uschar str_ioctet_c[]="i;octet";
static const struct String str_ioctet={ str_ioctet_c, 7 };
static uschar str_iascii_numeric_c[]="i;ascii-numeric";
static const struct String str_iascii_numeric={ str_iascii_numeric_c, 15 };
static uschar str_comparator_iascii_casemap_c[]="comparator-i;ascii-casemap";
static const struct String str_comparator_iascii_casemap={ str_comparator_iascii_casemap_c, 26 };
+static uschar str_comparator_enascii_casemap_c[]="comparator-en;ascii-casemap";
+static const struct String str_comparator_enascii_casemap={ str_comparator_enascii_casemap_c, 27 };
static uschar str_comparator_ioctet_c[]="comparator-i;octet";
static const struct String str_comparator_ioctet={ str_comparator_ioctet_c, 18 };
static uschar str_comparator_iascii_numeric_c[]="comparator-i;ascii-numeric";
if (hc&0x80) return 0;
#endif
/* tolower depends on the locale and only ASCII case must be insensitive */
- if ((nc&0x80) || (hc&0x80)) { if (nc!=hc) return 0; }
- else if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
+ if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
++n;
++h;
--nl;
/*************************************************
-* Octet-wise glob pattern search *
+* Glob pattern search *
*************************************************/
/*
Returns: 0 needle not found in haystack
1 needle found
+ -1 pattern error
*/
-static int eq_octetglob(const struct String *needle,
- const struct String *haystack)
+static int eq_glob(const struct String *needle,
+ const struct String *haystack, int ascii_caseless)
{
-struct String n,h;
+const uschar *n,*h,*nend,*hend;
+int may_advance=0;
-n=*needle;
-h=*haystack;
-while (n.length)
+n=needle->character;
+h=haystack->character;
+nend=n+needle->length;
+hend=h+haystack->length;
+while (n<nend)
{
- switch (n.character[0])
- {
- case '*':
- {
- int currentLength;
-
- ++n.character;
- --n.length;
- /* The greedy match is not yet well tested. Some day we may */
- /* need to refer to the matched parts, so the code is already */
- /* prepared for that. */
-#if 1
- /* greedy match */
- currentLength=h.length;
- h.character+=h.length;
- h.length=0;
- while (h.length<=currentLength)
- {
- if (eq_octetglob(&n,&h)) return 1;
- else /* go back one octet */
- {
- --h.character;
- ++h.length;
- }
- }
- return 0;
-#else
- /* minimal match */
- while (h.length)
- {
- if (eq_octetglob(&n,&h)) return 1;
- else /* advance one octet */
- {
- ++h.character;
- --h.length;
- }
- }
- break;
-#endif
- }
- case '?':
- {
- if (h.length)
- {
- ++h.character;
- --h.length;
- ++n.character;
- --n.length;
- }
- else return 0;
- break;
- }
- case '\\':
- {
- ++n.character;
- --n.length;
- /* FALLTHROUGH */
- }
- default:
- {
- if
- (
- h.length==0 ||
-#if !HAVE_ICONV
- (h.character[0]&0x80) || (n.character[0]&0x80) ||
-#endif
- h.character[0]!=n.character[0]
- ) return 0;
- else
- {
- ++h.character;
- --h.length;
- ++n.character;
- --n.length;
- };
- }
+ if (*n=='*')
+ {
+ ++n;
+ may_advance=1;
}
- }
-return (h.length==0);
-}
-
-
-/*************************************************
-* ASCII case-insensitive glob pattern search *
-*************************************************/
-
-/*
-Arguments:
- needle UTF-8 pattern to search ...
- haystack ... inside the haystack
-
-Returns: 0 needle not found in haystack
- 1 needle found
-*/
-
-static int eq_asciicaseglob(const struct String *needle,
- const struct String *haystack)
-{
-struct String n,h;
+ else
+ {
+ const uschar *npart,*hpart;
-n=*needle;
-h=*haystack;
-while (n.length)
- {
- switch (n.character[0])
- {
- case '*':
- {
- int currentLength;
-
- ++n.character;
- --n.length;
- /* The greedy match is not yet well tested. Some day we may */
- /* need to refer to the matched parts, so the code is already */
- /* prepared for that. */
-#if 1
- /* greedy match */
- currentLength=h.length;
- h.character+=h.length;
- h.length=0;
- while (h.length<=currentLength)
+ /* Try to match a non-star part of the needle at the current */
+ /* position in the haystack. */
+ match_part:
+ npart=n;
+ hpart=h;
+ while (npart<nend && *npart!='*') switch (*npart)
+ {
+ case '?':
{
- if (eq_asciicaseglob(&n,&h)) return 1;
- else /* go back one UTF-8 character */
+ if (hpart==hend) return 0;
+ /* watch out: Do not match one character, but one UTF8 encoded character */
+ if ((*hpart&0xc0)==0xc0)
{
- if (h.length==currentLength) return 0;
- --h.character;
- ++h.length;
- if (h.character[0]&0x80)
- {
- while (h.length<currentLength && (*(h.character-1)&0x80))
- {
- --h.character;
- ++h.length;
- }
- }
+ ++hpart;
+ while (hpart<hend && ((*hpart&0xc0)==0x80)) ++hpart;
}
+ else
+ ++hpart;
+ ++npart;
+ break;
}
- /* NOTREACHED */
-#else
- while (h.length)
+ case '\\':
{
- if (eq_asciicaseglob(&n,&h)) return 1;
- else /* advance one UTF-8 character */
- {
- if (h.character[0]&0x80)
- {
- while (h.length && (h.character[0]&0x80))
- {
- ++h.character;
- --h.length;
- }
- }
- else
- {
- ++h.character;
- --h.length;
- }
- }
+ ++npart;
+ if (npart==nend) return -1;
+ /* FALLTHROUGH */
}
- break;
-#endif
- }
- case '?':
- {
- if (h.length)
+ default:
{
- ++n.character;
- --n.length;
- /* advance one UTF-8 character */
- if (h.character[0]&0x80)
+ if (hpart==hend) return 0;
+ /* tolower depends on the locale, but we need ASCII */
+ if
+ (
+#if !HAVE_ICONV
+ (*hpart&0x80) || (*npart&0x80) ||
+#endif
+ ascii_caseless
+ ? ((*npart>='A' && *npart<='Z' ? *npart|0x20 : *npart) != (*hpart>='A' && *hpart<='Z' ? *hpart|0x20 : *hpart))
+ : *hpart!=*npart
+ )
{
- while (h.length && (h.character[0]&0x80))
+ if (may_advance)
+ /* string match after a star failed, advance and try again */
{
- ++h.character;
- --h.length;
+ ++h;
+ goto match_part;
}
+ else return 0;
}
else
{
- ++h.character;
- --h.length;
- }
+ ++npart;
+ ++hpart;
+ };
}
- else return 0;
- break;
}
- case '\\':
+ /* at this point, a part was matched successfully */
+ if (may_advance && npart==nend && hpart<hend)
+ /* needle ends, but haystack does not: if there was a star before, advance and try again */
{
- ++n.character;
- --n.length;
- /* FALLTHROUGH */
- }
- default:
- {
- char nc,hc;
-
- if (h.length==0) return 0;
- nc=n.character[0];
- hc=h.character[0];
-#if !HAVE_ICONV
- if ((hc&0x80) || (nc&0x80)) return 0;
-#endif
- /* tolower depends on the locale and only ASCII case must be insensitive */
- if ((nc&0x80) || (hc&0x80)) { if (nc!=hc) return 0; }
- else if ((nc>='A' && nc<='Z' ? nc|0x20 : nc) != (hc>='A' && hc<='Z' ? hc|0x20 : hc)) return 0;
- ++h.character;
- --h.length;
- ++n.character;
- --n.length;
+ ++h;
+ goto match_part;
}
+ h=hpart;
+ n=npart;
+ may_advance=0;
}
}
-return (h.length==0);
+return (h==hend ? 1 : may_advance);
}
switch (co)
{
case COMP_OCTET: debug_printf("i;octet"); break;
- case COMP_ASCII_CASEMAP: debug_printf("i;ascii-casemap"); break;
+ case COMP_EN_ASCII_CASEMAP: debug_printf("en;ascii-casemap"); break;
case COMP_ASCII_NUMERIC: debug_printf("i;ascii-numeric"); break;
}
debug_printf("\"):\n");
if (eq_octet(needle,haystack,0)) r=1;
break;
}
- case COMP_ASCII_CASEMAP:
+ case COMP_EN_ASCII_CASEMAP:
{
if (eq_asciicase(needle,haystack,0)) r=1;
break;
for (h=*haystack; h.length; ++h.character,--h.length) if (eq_octet(needle,&h,1)) { r=1; break; }
break;
}
- case COMP_ASCII_CASEMAP:
+ case COMP_EN_ASCII_CASEMAP:
{
for (h=*haystack; h.length; ++h.character,--h.length) if (eq_asciicase(needle,&h,1)) { r=1; break; }
break;
{
case COMP_OCTET:
{
- if (eq_octetglob(needle,haystack)) r=1;
+ if ((r=eq_glob(needle,haystack,0))==-1)
+ {
+ filter->errmsg=CUS "syntactically invalid pattern";
+ return -1;
+ }
break;
}
- case COMP_ASCII_CASEMAP:
+ case COMP_EN_ASCII_CASEMAP:
{
- if (eq_asciicaseglob(needle,haystack)) r=1;
+ if ((r=eq_glob(needle,haystack,1))==-1)
+ {
+ filter->errmsg=CUS "syntactically invalid pattern";
+ return -1;
+ }
break;
}
default:
}
else if (eq_asciicase(&comparator_name,&str_iascii_casemap,0))
{
- *c=COMP_ASCII_CASEMAP;
+ *c=COMP_EN_ASCII_CASEMAP;
+ match=1;
+ }
+ else if (eq_asciicase(&comparator_name,&str_enascii_casemap,0))
+ {
+ *c=COMP_EN_ASCII_CASEMAP;
match=1;
}
else if (eq_asciicase(&comparator_name,&str_iascii_numeric,0))
*/
enum AddressPart addressPart=ADDRPART_ALL;
- enum Comparator comparator=COMP_ASCII_CASEMAP;
+ enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
enum MatchType matchType=MATCH_IS;
struct String *hdr,*h,*key,*k;
int m;
<header-names: string-list> <key-list: string-list>
*/
- enum Comparator comparator=COMP_ASCII_CASEMAP;
+ enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
enum MatchType matchType=MATCH_IS;
struct String *hdr,*h,*key,*k;
int m;
envelope-part is case insensitive "from" or "to"
*/
- enum Comparator comparator=COMP_ASCII_CASEMAP;
+ enum Comparator comparator=COMP_EN_ASCII_CASEMAP;
enum AddressPart addressPart=ADDRPART_ALL;
enum MatchType matchType=MATCH_IS;
struct String *env,*e,*key,*k;
envelopeStr.character=envelope;
envelopeStr.length=Ustrlen(envelope);
- *cond=compare(filter,&envelopeStr,k,comparator,matchType);
+ *cond=compare(filter,k,&envelopeStr,comparator,matchType);
if (*cond==-1) return -1;
if (*cond) break;
}
fileinto-command = "fileinto" { fileinto-options } string ";"
fileinto-options =
fileinto-options =) [ ":copy" ]
- */
+ */
struct String folder;
uschar *s;
/*
vacation-command = "vacation" { vacation-options } <reason: string> ";"
vacation-options = [":days" number]
- [":addresses" string-list]
[":subject" string]
+ [":from" string]
+ [":addresses" string-list]
[":mime"]
+ [":handle" string]
*/
int m;
unsigned long days;
- struct String *addresses;
struct String subject;
+ struct String from;
+ struct String *addresses;
int reason_is_mime;
string_item *aliases;
+ struct String handle;
struct String reason;
if (!filter->require_vacation)
filter->vacation_ran=1;
}
days=VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
- addresses=(struct String*)0;
subject.character=(uschar*)0;
subject.length=-1;
+ from.character=(uschar*)0;
+ from.length=-1;
+ addresses=(struct String*)0;
aliases=NULL;
reason_is_mime=0;
+ handle.character=(uschar*)0;
+ handle.length=-1;
for (;;)
{
if (parse_white(filter)==-1) return -1;
if (days<VACATION_MIN_DAYS) days=VACATION_MIN_DAYS;
else if (days>VACATION_MAX_DAYS) days=VACATION_MAX_DAYS;
}
+ else if (parse_identifier(filter,CUS ":subject")==1)
+ {
+ if (parse_white(filter)==-1) return -1;
+ if ((m=parse_string(filter,&subject))!=1)
+ {
+ if (m==0) filter->errmsg=CUS "subject string expected";
+ return -1;
+ }
+ }
+ else if (parse_identifier(filter,CUS ":from")==1)
+ {
+ int start, end, domain;
+ uschar *error,*ss;
+
+ if (parse_white(filter)==-1) return -1;
+ if ((m=parse_string(filter,&from))!=1)
+ {
+ if (m==0) filter->errmsg=CUS "from string expected";
+ return -1;
+ }
+ if (from.length>0)
+ {
+ ss = parse_extract_address(from.character, &error, &start, &end, &domain,
+ FALSE);
+ if (ss == NULL)
+ {
+ filter->errmsg=string_sprintf("malformed address \"%s\" in "
+ "Sieve filter: %s", from.character, error);
+ return -1;
+ }
+ }
+ else
+ {
+ filter->errmsg=CUS "empty :from address in Sieve filter";
+ return -1;
+ }
+ }
else if (parse_identifier(filter,CUS ":addresses")==1)
{
struct String *a;
aliases=new;
}
}
- else if (parse_identifier(filter,CUS ":subject")==1)
+ else if (parse_identifier(filter,CUS ":mime")==1)
+ reason_is_mime=1;
+ else if (parse_identifier(filter,CUS ":handle")==1)
{
if (parse_white(filter)==-1) return -1;
- if ((m=parse_string(filter,&subject))!=1)
+ if ((m=parse_string(filter,&from))!=1)
{
- if (m==0) filter->errmsg=CUS "subject string expected";
+ if (m==0) filter->errmsg=CUS "handle string expected";
return -1;
}
}
- else if (parse_identifier(filter,CUS ":mime")==1)
- reason_is_mime=1;
else break;
}
if (parse_white(filter)==-1) return -1;
if (m==0) filter->errmsg=CUS "missing reason string";
return -1;
}
+ if (reason_is_mime)
+ {
+ uschar *s,*end;
+
+ for (s=reason.character,end=reason.character+reason.length; s<end && (*s&0x80)==0; ++s);
+ if (s<end)
+ {
+ filter->errmsg=CUS "MIME reason string contains 8bit text";
+ return -1;
+ }
+ }
if (parse_semicolon(filter)==-1) return -1;
if (exec)
uschar *buffer;
int buffer_capacity;
struct String key;
- struct String *a;
md5 base;
uschar digest[16];
uschar hexdigest[33];
key.character=(uschar*)0;
key.length=0;
capacity=0;
- if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
- key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
- key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
- if (addresses!=(struct String*)0) for (a=addresses; a->length!=-1; ++a)
+ if (handle.length==-1)
{
- key.character=string_cat(key.character,&capacity,&key.length,US":",1);
- key.character=string_cat(key.character,&capacity,&key.length,a->character,a->length);
+ if (subject.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,subject.character,subject.length);
+ if (from.length!=-1) key.character=string_cat(key.character,&capacity,&key.length,from.character,from.length);
+ key.character=string_cat(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
+ key.character=string_cat(key.character,&capacity,&key.length,reason.character,reason.length);
}
+ else
+ key=handle;
md5_start(&base);
md5_end(&base, key.character, key.length, digest);
for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
if (subject.length==-1)
{
expand_header(&subject,&str_subject);
- while (subject.length>=4 && Ustrncmp(subject.character,"Re: ",4)==0)
- {
- subject.character+=4;
- subject.length-=4;
- }
capacity=6;
start=6;
subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
addr->reply = store_get(sizeof(reply_item));
memset(addr->reply,0,sizeof(reply_item)); /* XXX */
addr->reply->to = string_copy(sender_address);
- addr->reply->from = expand_string(US"$local_part@$domain");
+ if (from.length==-1)
+ addr->reply->from = expand_string(US"$local_part@$domain");
+ else
+ addr->reply->from = from.character;
/* Allocation is larger than neccessary, but enough even for split MIME words */
buffer_capacity=16+4*subject.length;
buffer=store_get(buffer_capacity);
}
for (check=cap; check->character; ++check)
{
- if (eq_asciicase(check,&str_envelope,0)) filter->require_envelope=1;
- else if (eq_asciicase(check,&str_fileinto,0)) filter->require_fileinto=1;
+ if (eq_octet(check,&str_envelope,0)) filter->require_envelope=1;
+ else if (eq_octet(check,&str_fileinto,0)) filter->require_fileinto=1;
#ifdef SUBADDRESS
- else if (eq_asciicase(check,&str_subaddress,0)) filter->require_subaddress=1;
+ else if (eq_octet(check,&str_subaddress,0)) filter->require_subaddress=1;
#endif
#ifdef VACATION
- else if (eq_asciicase(check,&str_vacation,0))
+ else if (eq_octet(check,&str_vacation,0))
{
if (filter_test == FTEST_NONE && filter->vacation_directory == NULL)
{
filter->require_vacation=1;
}
#endif
- else if (eq_asciicase(check,&str_copy,0)) filter->require_copy=1;
- else if (eq_asciicase(check,&str_comparator_ioctet,0)) ;
- else if (eq_asciicase(check,&str_comparator_iascii_casemap,0)) ;
- else if (eq_asciicase(check,&str_comparator_iascii_numeric,0)) filter->require_iascii_numeric=1;
+ else if (eq_octet(check,&str_copy,0)) filter->require_copy=1;
+ else if (eq_octet(check,&str_comparator_ioctet,0)) ;
+ else if (eq_octet(check,&str_comparator_iascii_casemap,0)) ;
+ else if (eq_octet(check,&str_comparator_enascii_casemap,0)) ;
+ else if (eq_octet(check,&str_comparator_iascii_numeric,0)) filter->require_iascii_numeric=1;
else
{
filter->errmsg=CUS "unknown capability";
if (sieve.keep)
{
add_addr(generated,US"inbox",1,0,0,0);
- msg = string_sprintf("Keep");
+ msg = string_sprintf("Implicit keep");
r = FF_DELIVERED;
}
- else
+ else
{
- msg = string_sprintf("No keep");
+ msg = string_sprintf("No implicit keep");
r = FF_DELIVERED;
}
}