-/* $Cambridge: exim/src/src/sieve.c,v 1.4 2005/02/15 09:31:13 ph10 Exp $ */
+/* $Cambridge: exim/src/src/sieve.c,v 1.12 2005/06/17 10:47:05 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Michael Haardt 2003,2004 */
+/* Copyright (c) Michael Haardt 2003-2005 */
/* See the file NOTICE for conditions of use and distribution. */
/* This code was contributed by Michael Haardt. */
#undef RFC_EOL
/* Define this for development of the subaddress Sieve extension. */
-/* The code is currently broken. */
-#undef SUBADDRESS
+#define SUBADDRESS
-/* Define this for development of the vacation Sieve extension. */
-/* The code is not yet finished. */
+/* Define this for the vacation Sieve extension. */
#define VACATION
/* Must be >= 1 */
int vacation_ran;
#endif
uschar *vacation_directory;
+ const uschar *subaddress;
+ const uschar *useraddress;
int require_copy;
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";
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;
if (eq_octetglob(needle,haystack)) r=1;
break;
}
- case COMP_ASCII_CASEMAP:
+ case COMP_EN_ASCII_CASEMAP:
{
if (eq_asciicaseglob(needle,haystack)) r=1;
break;
}
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;
case ADDRPART_LOCALPART: part=extracted_addr; part[domain-1]='\0'; break;
case ADDRPART_DOMAIN: part=extracted_addr+domain; break;
#ifdef SUBADDRESS
- case ADDRPART_DETAIL:
- part=NULL;
+ case ADDRPART_DETAIL: part=NULL; break;
#endif
- break;
}
*end_addr = saveend;
<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;
case ADDRPART_LOCALPART: envelopeExpr=CUS "${local_part:$sender_address}"; break;
case ADDRPART_DOMAIN: envelopeExpr=CUS "${domain:$sender_address}"; break;
#ifdef SUBADDRESS
- case ADDRPART_DETAIL:
- envelopeExpr=CUS 0;
- break;
+ case ADDRPART_DETAIL: envelopeExpr=CUS 0; break;
#endif
}
}
{
case ADDRPART_ALL: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
#ifdef SUBADDRESS
- case ADDRPART_USER: envelopeExpr=CUS "$local_part_prefix$local_part"; break;
- case ADDRPART_DETAIL: envelopeExpr=CUS "$local_part_suffix"; break;
+ case ADDRPART_USER: envelopeExpr=filter->useraddress; break;
+ case ADDRPART_DETAIL: envelopeExpr=filter->subaddress; break;
#endif
case ADDRPART_LOCALPART: envelopeExpr=CUS "$local_part_prefix$local_part$local_part_suffix"; break;
case ADDRPART_DOMAIN: envelopeExpr=CUS "$domain"; break;
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;
}
}
else
{
- filter->errmsg=CUS "expecting command or closing brace";
+ filter->errmsg=CUS "expecting command or closing brace";
return -1;
}
}
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*)0;
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)
days=VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
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)
if (filter_personal(aliases,TRUE))
{
+ if (filter_test == FTEST_NONE)
+ {
+ /* ensure oncelog directory exists; failure will be detected later */
- /* ensure oncelog directory exists; failure will be detected later */
-
- (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
-
+ (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
+ }
/* build oncelog filename */
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 (handle.length==-1)
+ {
+ 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]);
- capacity=Ustrlen(filter->vacation_directory);
- start=capacity;
- once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
- once=string_cat(once,&capacity,&start,hexdigest,33);
-
- /* process subject */
-
- if (subject.length==-1)
+ if (filter_test != FTEST_NONE)
{
- 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);
- subject.length=start;
+ debug_printf("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
}
-
- /* add address to list of generated addresses */
-
- addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
- setflag(addr, af_pfr);
- setflag(addr, af_ignore_error);
- addr->next = *generated;
- *generated = addr;
- 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");
- /* Allocation is larger than neccessary, but enough even for split MIME words */
- buffer_capacity=16+4*subject.length;
- buffer=store_get(buffer_capacity);
- addr->reply->subject=parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity);
- addr->reply->oncelog=once;
- addr->reply->once_repeat=days*86400;
-
- /* build body and MIME headers */
-
- if (reason_is_mime)
+ else
{
- uschar *mime_body,*reason_end;
+ capacity=Ustrlen(filter->vacation_directory);
+ start=capacity;
+ once=string_cat(filter->vacation_directory,&capacity,&start,US"/",1);
+ once=string_cat(once,&capacity,&start,hexdigest,33);
+ once[start] = '\0';
+
+ /* process subject */
+
+ if (subject.length==-1)
+ {
+ expand_header(&subject,&str_subject);
+ capacity=6;
+ start=6;
+ subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
+ subject.length=start;
+ }
+
+ /* add address to list of generated addresses */
+
+ addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
+ setflag(addr, af_pfr);
+ setflag(addr, af_ignore_error);
+ addr->next = *generated;
+ *generated = addr;
+ addr->reply = store_get(sizeof(reply_item));
+ memset(addr->reply,0,sizeof(reply_item)); /* XXX */
+ addr->reply->to = string_copy(sender_address);
+ 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);
+ addr->reply->subject=parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity);
+ addr->reply->oncelog=once;
+ addr->reply->once_repeat=days*86400;
+
+ /* build body and MIME headers */
+
+ if (reason_is_mime)
+ {
+ uschar *mime_body,*reason_end;
#ifdef RFC_EOL
- static const uschar nlnl[]="\r\n\r\n";
+ static const uschar nlnl[]="\r\n\r\n";
#else
- static const uschar nlnl[]="\n\n";
+ static const uschar nlnl[]="\n\n";
#endif
- for
- (
- mime_body=reason.character,reason_end=reason.character+reason.length;
- mime_body<(reason_end-sizeof(nlnl)-1) && memcmp(mime_body,nlnl,sizeof(nlnl)-1);
- ++mime_body
- );
- capacity = 0;
- start = 0;
- addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
- capacity = 0;
- start = 0;
- if (mime_body<reason_end) mime_body+=sizeof(nlnl)-1;
- addr->reply->text = string_cat(NULL,&capacity,&start,mime_body,reason_end-mime_body);
+ for
+ (
+ mime_body=reason.character,reason_end=reason.character+reason.length;
+ mime_body<(reason_end-sizeof(nlnl)-1) && memcmp(mime_body,nlnl,sizeof(nlnl)-1);
+ ++mime_body
+ );
+ capacity = 0;
+ start = 0;
+ addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
+ addr->reply->headers[start] = '\0';
+ capacity = 0;
+ start = 0;
+ if (mime_body+(sizeof(nlnl)-1)<reason_end) mime_body+=sizeof(nlnl)-1;
+ else mime_body=reason_end-1;
+ addr->reply->text = string_cat(NULL,&capacity,&start,mime_body,reason_end-mime_body);
+ addr->reply->text[start] = '\0';
+ }
+ else
+ {
+ struct String qp;
+
+ capacity = 0;
+ start = reason.length;
+ addr->reply->headers = US"MIME-Version: 1.0\n"
+ "Content-Type: text/plain;\n"
+ "\tcharset=\"utf-8\"\n"
+ "Content-Transfer-Encoding: quoted-printable";
+ addr->reply->text = quoted_printable_encode(&reason,&qp)->character;
+ }
}
- else
+ }
+ else if (filter_test != FTEST_NONE)
{
- struct String qp;
-
- capacity = 0;
- start = reason.length;
- addr->reply->headers = US"MIME-Version: 1.0\n"
- "Content-Type: text/plain;\n"
- "\tcharset=\"utf-8\"\n"
- "Content-Transfer-Encoding: quoted-printable";
- addr->reply->text = quoted_printable_encode(&reason,&qp)->character;
+ debug_printf("Sieve: mail was not personal, vacation would ignore it\n");
}
- }
}
}
+ else break;
#endif
- else break;
}
return 1;
}
if (parse_white(filter)==-1) return -1;
-if (exec && filter->vacation_directory != NULL) /* 2nd test added by PH */
+if (exec && filter->vacation_directory != NULL && filter_test == FTEST_NONE)
{
DIR *oncelogdir;
struct dirent *oncelog;
}
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->vacation_directory == NULL)
+ if (filter_test == FTEST_NONE && filter->vacation_directory == NULL)
{
filter->errmsg=CUS "vacation disabled";
return -1;
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";
options controls whether various special things are allowed, and requests
special actions (not currently used)
sieve_vacation_directory where to store vacation "once" files
+ useraddress string expression for :user part of address
+ subaddress string expression for :subaddress part of address
generated where to hang newly-generated addresses
error where to pass back an error text
int
sieve_interpret(uschar *filter, int options, uschar *vacation_directory,
- address_item **generated, uschar **error)
+ uschar *useraddress, uschar *subaddress, address_item **generated, uschar **error)
{
struct Sieve sieve;
int r;
}
}
+sieve.useraddress = useraddress == NULL ? CUS "$local_part_prefix$local_part$local_part_suffix" : useraddress;
+sieve.subaddress = subaddress;
+
#ifdef COMPILE_SYNTAX_CHECKER
if (parse_start(&sieve,0,generated)==1)
#else